@prismatic-io/lux 0.0.2-preview.14 → 0.0.2-preview.16
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/lib/answerers/claude-code/index.d.ts +14 -2
- package/lib/answerers/claude-code/index.d.ts.map +1 -1
- package/lib/answerers/claude-code/index.js +3 -3
- package/lib/answerers/claude-code/index.js.map +1 -1
- package/lib/answerers/persona/index.d.ts +28 -2
- package/lib/answerers/persona/index.d.ts.map +1 -1
- package/lib/answerers/persona/index.js +3 -3
- package/lib/answerers/persona/index.js.map +1 -1
- package/lib/answerers/terminal/index.d.ts +8 -2
- package/lib/answerers/terminal/index.d.ts.map +1 -1
- package/lib/answerers/terminal/index.js +3 -2
- package/lib/answerers/terminal/index.js.map +1 -1
- package/lib/assertions/core/resource-checks.d.ts.map +1 -1
- package/lib/assertions/core/resource-checks.js +3 -5
- package/lib/assertions/core/resource-checks.js.map +1 -1
- package/lib/assertions/rubric/index.d.ts.map +1 -1
- package/lib/assertions/rubric/index.js +7 -1
- package/lib/assertions/rubric/index.js.map +1 -1
- package/lib/assertions/rubric/internal.d.ts +16 -5
- package/lib/assertions/rubric/internal.d.ts.map +1 -1
- package/lib/assertions/rubric/internal.js +10 -6
- package/lib/assertions/rubric/internal.js.map +1 -1
- package/lib/authoring.d.ts +32 -2
- package/lib/authoring.d.ts.map +1 -1
- package/lib/authoring.js +33 -1
- package/lib/authoring.js.map +1 -1
- package/lib/cli/bin.js +2 -13
- package/lib/cli/bin.js.map +1 -1
- package/lib/cli/command-runtime.d.ts +3 -3
- package/lib/cli/command-runtime.d.ts.map +1 -1
- package/lib/cli/command-runtime.js +17 -6
- package/lib/cli/command-runtime.js.map +1 -1
- package/lib/cli/init-templates.d.ts +11 -0
- package/lib/cli/init-templates.d.ts.map +1 -0
- package/lib/cli/init-templates.js +207 -0
- package/lib/cli/init-templates.js.map +1 -0
- package/lib/cli/init.d.ts +7 -0
- package/lib/cli/init.d.ts.map +1 -1
- package/lib/cli/init.js +23 -173
- package/lib/cli/init.js.map +1 -1
- package/lib/cli/output-schemas.d.ts +1399 -0
- package/lib/cli/output-schemas.d.ts.map +1 -0
- package/lib/cli/output-schemas.js +91 -0
- package/lib/cli/output-schemas.js.map +1 -0
- package/lib/cli/program.d.ts +135 -4
- package/lib/cli/program.d.ts.map +1 -1
- package/lib/cli/program.js +725 -520
- package/lib/cli/program.js.map +1 -1
- package/lib/cli/render/reporter.d.ts.map +1 -1
- package/lib/cli/render/reporter.js +24 -4
- package/lib/cli/render/reporter.js.map +1 -1
- package/lib/cli/run-options.d.ts +0 -4
- package/lib/cli/run-options.d.ts.map +1 -1
- package/lib/cli/run-options.js +8 -19
- package/lib/cli/run-options.js.map +1 -1
- package/lib/cli/view.d.ts +5 -3
- package/lib/cli/view.d.ts.map +1 -1
- package/lib/cli/view.js +120 -78
- package/lib/cli/view.js.map +1 -1
- package/lib/core/annotation.d.ts +6 -2
- package/lib/core/annotation.d.ts.map +1 -1
- package/lib/core/annotation.js +2 -1
- package/lib/core/annotation.js.map +1 -1
- package/lib/core/answerer.d.ts +12 -0
- package/lib/core/answerer.d.ts.map +1 -1
- package/lib/core/answerer.js +2 -0
- package/lib/core/answerer.js.map +1 -1
- package/lib/core/assertion.d.ts +3 -0
- package/lib/core/assertion.d.ts.map +1 -1
- package/lib/core/assertion.js +3 -0
- package/lib/core/assertion.js.map +1 -1
- package/lib/core/case.d.ts +24 -2
- package/lib/core/case.d.ts.map +1 -1
- package/lib/core/case.js +5 -1
- package/lib/core/case.js.map +1 -1
- package/lib/core/driver.d.ts +40 -0
- package/lib/core/driver.d.ts.map +1 -1
- package/lib/core/driver.js +12 -0
- package/lib/core/driver.js.map +1 -1
- package/lib/core/environment.d.ts +18 -0
- package/lib/core/environment.d.ts.map +1 -0
- package/lib/core/environment.js +21 -0
- package/lib/core/environment.js.map +1 -0
- package/lib/core/evaluation-clusters.d.ts +17 -0
- package/lib/core/evaluation-clusters.d.ts.map +1 -0
- package/lib/core/evaluation-clusters.js +23 -0
- package/lib/core/evaluation-clusters.js.map +1 -0
- package/lib/core/experiment.d.ts +11 -0
- package/lib/core/experiment.d.ts.map +1 -1
- package/lib/core/experiment.js +31 -17
- package/lib/core/experiment.js.map +1 -1
- package/lib/core/file-tree.d.ts +7 -0
- package/lib/core/file-tree.d.ts.map +1 -0
- package/lib/core/file-tree.js +35 -0
- package/lib/core/file-tree.js.map +1 -0
- package/lib/core/index.d.ts +1 -0
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +1 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/lifecycle-fixtures.d.ts.map +1 -1
- package/lib/core/lifecycle-fixtures.js +4 -2
- package/lib/core/lifecycle-fixtures.js.map +1 -1
- package/lib/core/platform-process.d.ts +5 -1
- package/lib/core/platform-process.d.ts.map +1 -1
- package/lib/core/platform-process.js +48 -10
- package/lib/core/platform-process.js.map +1 -1
- package/lib/core/run.d.ts +93 -1
- package/lib/core/run.d.ts.map +1 -1
- package/lib/core/run.js +16 -0
- package/lib/core/run.js.map +1 -1
- package/lib/core/usage.d.ts +5 -0
- package/lib/core/usage.d.ts.map +1 -1
- package/lib/core/usage.js +3 -0
- package/lib/core/usage.js.map +1 -1
- package/lib/drivers/claude-code/index.d.ts +65 -2
- package/lib/drivers/claude-code/index.d.ts.map +1 -1
- package/lib/drivers/claude-code/index.js +30 -3
- package/lib/drivers/claude-code/index.js.map +1 -1
- package/lib/drivers/codex/app-events.d.ts.map +1 -1
- package/lib/drivers/codex/app-events.js +41 -30
- package/lib/drivers/codex/app-events.js.map +1 -1
- package/lib/drivers/codex/exec-events.d.ts +4 -0
- package/lib/drivers/codex/exec-events.d.ts.map +1 -0
- package/lib/drivers/codex/exec-events.js +230 -0
- package/lib/drivers/codex/exec-events.js.map +1 -0
- package/lib/drivers/codex/index.d.ts +76 -4
- package/lib/drivers/codex/index.d.ts.map +1 -1
- package/lib/drivers/codex/index.js +35 -232
- package/lib/drivers/codex/index.js.map +1 -1
- package/lib/drivers/cursor/acp-transport.d.ts +38 -0
- package/lib/drivers/cursor/acp-transport.d.ts.map +1 -0
- package/lib/drivers/cursor/acp-transport.js +152 -0
- package/lib/drivers/cursor/acp-transport.js.map +1 -0
- package/lib/drivers/cursor/config.d.ts +22 -0
- package/lib/drivers/cursor/config.d.ts.map +1 -0
- package/lib/drivers/cursor/config.js +26 -0
- package/lib/drivers/cursor/config.js.map +1 -0
- package/lib/drivers/cursor/events.d.ts +13 -0
- package/lib/drivers/cursor/events.d.ts.map +1 -0
- package/lib/drivers/cursor/events.js +73 -0
- package/lib/drivers/cursor/events.js.map +1 -0
- package/lib/drivers/cursor/index.d.ts +37 -0
- package/lib/drivers/cursor/index.d.ts.map +1 -0
- package/lib/drivers/cursor/index.js +276 -0
- package/lib/drivers/cursor/index.js.map +1 -0
- package/lib/drivers/cursor/interaction.d.ts +7 -0
- package/lib/drivers/cursor/interaction.d.ts.map +1 -0
- package/lib/drivers/cursor/interaction.js +66 -0
- package/lib/drivers/cursor/interaction.js.map +1 -0
- package/lib/drivers/mcp/index.d.ts +35 -2
- package/lib/drivers/mcp/index.d.ts.map +1 -1
- package/lib/drivers/mcp/index.js +16 -3
- package/lib/drivers/mcp/index.js.map +1 -1
- package/lib/drivers/shared/experiment-behavior.d.ts +13 -0
- package/lib/drivers/shared/experiment-behavior.d.ts.map +1 -0
- package/lib/drivers/shared/experiment-behavior.js +197 -0
- package/lib/drivers/shared/experiment-behavior.js.map +1 -0
- package/lib/drivers/shared/experiment-policy.d.ts +4 -0
- package/lib/drivers/shared/experiment-policy.d.ts.map +1 -0
- package/lib/drivers/shared/experiment-policy.js +143 -0
- package/lib/drivers/shared/experiment-policy.js.map +1 -0
- package/lib/drivers/subprocess/index.d.ts +37 -2
- package/lib/drivers/subprocess/index.d.ts.map +1 -1
- package/lib/drivers/subprocess/index.js +46 -6
- package/lib/drivers/subprocess/index.js.map +1 -1
- package/lib/index.d.ts +6 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -3
- package/lib/index.js.map +1 -1
- package/lib/orchestrator/annotation-loader.d.ts +6 -2
- package/lib/orchestrator/annotation-loader.d.ts.map +1 -1
- package/lib/orchestrator/annotation-loader.js +8 -0
- package/lib/orchestrator/annotation-loader.js.map +1 -1
- package/lib/orchestrator/annotation-store.d.ts.map +1 -1
- package/lib/orchestrator/annotation-store.js +16 -2
- package/lib/orchestrator/annotation-store.js.map +1 -1
- package/lib/orchestrator/campaign-lifecycle.d.ts +1 -0
- package/lib/orchestrator/campaign-lifecycle.d.ts.map +1 -1
- package/lib/orchestrator/campaign-lifecycle.js +4 -1
- package/lib/orchestrator/campaign-lifecycle.js.map +1 -1
- package/lib/orchestrator/compare.d.ts +4 -0
- package/lib/orchestrator/compare.d.ts.map +1 -1
- package/lib/orchestrator/compare.js +13 -1
- package/lib/orchestrator/compare.js.map +1 -1
- package/lib/orchestrator/comparison-identity.d.ts.map +1 -1
- package/lib/orchestrator/comparison-identity.js +1 -0
- package/lib/orchestrator/comparison-identity.js.map +1 -1
- package/lib/orchestrator/config.d.ts +42 -0
- package/lib/orchestrator/config.d.ts.map +1 -1
- package/lib/orchestrator/config.js +6 -2
- package/lib/orchestrator/config.js.map +1 -1
- package/lib/orchestrator/discover.d.ts +1 -0
- package/lib/orchestrator/discover.d.ts.map +1 -1
- package/lib/orchestrator/discover.js +7 -1
- package/lib/orchestrator/discover.js.map +1 -1
- package/lib/orchestrator/doctor.d.ts.map +1 -1
- package/lib/orchestrator/doctor.js +34 -26
- package/lib/orchestrator/doctor.js.map +1 -1
- package/lib/orchestrator/driver-capabilities.d.ts +3 -0
- package/lib/orchestrator/driver-capabilities.d.ts.map +1 -0
- package/lib/orchestrator/driver-capabilities.js +10 -0
- package/lib/orchestrator/driver-capabilities.js.map +1 -0
- package/lib/orchestrator/driver-identity.d.ts +5 -0
- package/lib/orchestrator/driver-identity.d.ts.map +1 -0
- package/lib/orchestrator/driver-identity.js +41 -0
- package/lib/orchestrator/driver-identity.js.map +1 -0
- package/lib/orchestrator/experiment-context.d.ts +10 -0
- package/lib/orchestrator/experiment-context.d.ts.map +1 -1
- package/lib/orchestrator/experiment-contracts.d.ts +10 -0
- package/lib/orchestrator/experiment-contracts.d.ts.map +1 -1
- package/lib/orchestrator/experiment-corpus.d.ts.map +1 -1
- package/lib/orchestrator/experiment-corpus.js +17 -19
- package/lib/orchestrator/experiment-corpus.js.map +1 -1
- package/lib/orchestrator/experiment-effects.d.ts +33 -0
- package/lib/orchestrator/experiment-effects.d.ts.map +1 -0
- package/lib/orchestrator/experiment-effects.js +44 -0
- package/lib/orchestrator/experiment-effects.js.map +1 -0
- package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.js +36 -3
- package/lib/orchestrator/experiment-evaluation.js.map +1 -1
- package/lib/orchestrator/experiment-identity.d.ts.map +1 -1
- package/lib/orchestrator/experiment-identity.js +18 -29
- package/lib/orchestrator/experiment-identity.js.map +1 -1
- package/lib/orchestrator/experiment-loader.d.ts +7 -2
- package/lib/orchestrator/experiment-loader.d.ts.map +1 -1
- package/lib/orchestrator/experiment-report.d.ts +70 -0
- package/lib/orchestrator/experiment-report.d.ts.map +1 -1
- package/lib/orchestrator/experiment-report.js +9 -1
- package/lib/orchestrator/experiment-report.js.map +1 -1
- package/lib/orchestrator/experiment-runs.d.ts +22 -1
- package/lib/orchestrator/experiment-runs.d.ts.map +1 -1
- package/lib/orchestrator/experiment-runs.js +13 -5
- package/lib/orchestrator/experiment-runs.js.map +1 -1
- package/lib/orchestrator/experiment-runtime-identity.d.ts +0 -16
- package/lib/orchestrator/experiment-runtime-identity.d.ts.map +1 -1
- package/lib/orchestrator/experiment-runtime-identity.js +5 -220
- package/lib/orchestrator/experiment-runtime-identity.js.map +1 -1
- package/lib/orchestrator/experiment-selection.d.ts.map +1 -1
- package/lib/orchestrator/experiment-selection.js +52 -0
- package/lib/orchestrator/experiment-selection.js.map +1 -1
- package/lib/orchestrator/experiment.d.ts +0 -2
- package/lib/orchestrator/experiment.d.ts.map +1 -1
- package/lib/orchestrator/experiment.js +23 -15
- package/lib/orchestrator/experiment.js.map +1 -1
- package/lib/orchestrator/fs-read.d.ts +1 -6
- package/lib/orchestrator/fs-read.d.ts.map +1 -1
- package/lib/orchestrator/fs-read.js +2 -32
- package/lib/orchestrator/fs-read.js.map +1 -1
- package/lib/orchestrator/grader-experiment.d.ts +17 -1
- package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
- package/lib/orchestrator/grader-experiment.js +52 -7
- package/lib/orchestrator/grader-experiment.js.map +1 -1
- package/lib/orchestrator/grading.d.ts.map +1 -1
- package/lib/orchestrator/grading.js +13 -6
- package/lib/orchestrator/grading.js.map +1 -1
- package/lib/orchestrator/list-runs.d.ts +1 -0
- package/lib/orchestrator/list-runs.d.ts.map +1 -1
- package/lib/orchestrator/list-runs.js +27 -6
- package/lib/orchestrator/list-runs.js.map +1 -1
- package/lib/orchestrator/optimization-lifecycle.d.ts.map +1 -1
- package/lib/orchestrator/optimization-lifecycle.js +3 -1
- package/lib/orchestrator/optimization-lifecycle.js.map +1 -1
- package/lib/orchestrator/orchestrator.d.ts +6 -1
- package/lib/orchestrator/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator/orchestrator.js +17 -143
- package/lib/orchestrator/orchestrator.js.map +1 -1
- package/lib/orchestrator/paired-effects.d.ts +34 -0
- package/lib/orchestrator/paired-effects.d.ts.map +1 -0
- package/lib/orchestrator/paired-effects.js +158 -0
- package/lib/orchestrator/paired-effects.js.map +1 -0
- package/lib/orchestrator/provenance.d.ts +5 -1
- package/lib/orchestrator/provenance.d.ts.map +1 -1
- package/lib/orchestrator/provenance.js +16 -3
- package/lib/orchestrator/provenance.js.map +1 -1
- package/lib/orchestrator/regrade.d.ts +1 -0
- package/lib/orchestrator/regrade.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.d.ts +2 -0
- package/lib/orchestrator/run-execution.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.js +18 -3
- package/lib/orchestrator/run-execution.js.map +1 -1
- package/lib/orchestrator/run-lifecycle.d.ts +1 -0
- package/lib/orchestrator/run-lifecycle.d.ts.map +1 -1
- package/lib/orchestrator/run-lifecycle.js +3 -2
- package/lib/orchestrator/run-lifecycle.js.map +1 -1
- package/lib/orchestrator/suite-runs.d.ts +3 -1
- package/lib/orchestrator/suite-runs.d.ts.map +1 -1
- package/lib/orchestrator/suite-runs.js +12 -2
- package/lib/orchestrator/suite-runs.js.map +1 -1
- package/lib/orchestrator/suite-summary.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.js +2 -1
- package/lib/orchestrator/suite-summary.js.map +1 -1
- package/lib/previewer/server.d.ts +6 -0
- package/lib/previewer/server.d.ts.map +1 -1
- package/lib/previewer/server.js +21 -5
- package/lib/previewer/server.js.map +1 -1
- package/package.json +2 -2
- package/skills/lux/references/cli.md +5 -2
- package/skills/lux/references/eval-authoring.md +0 -7
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/answerers/claude-code/index.ts +3 -3
- package/src/answerers/persona/index.ts +3 -3
- package/src/answerers/terminal/index.ts +4 -9
- package/src/assertions/core/resource-checks.ts +3 -5
- package/src/assertions/rubric/index.ts +7 -1
- package/src/assertions/rubric/internal.ts +13 -8
- package/src/authoring.ts +331 -2
- package/src/cli/bin.ts +2 -12
- package/src/cli/command-runtime.ts +20 -9
- package/src/cli/init-templates.ts +223 -0
- package/src/cli/init.ts +38 -184
- package/src/cli/output-schemas.ts +106 -0
- package/src/cli/program.ts +621 -548
- package/src/cli/render/reporter.ts +25 -4
- package/src/cli/run-options.ts +8 -25
- package/src/cli/view.ts +45 -84
- package/src/core/annotation.ts +2 -1
- package/src/core/answerer.ts +9 -0
- package/src/core/assertion.ts +3 -0
- package/src/core/case.ts +5 -1
- package/src/core/driver.ts +38 -0
- package/src/core/environment.ts +22 -0
- package/src/core/evaluation-clusters.ts +35 -0
- package/src/core/experiment.ts +60 -23
- package/src/core/file-tree.ts +35 -0
- package/src/core/index.ts +1 -0
- package/src/core/lifecycle-fixtures.ts +4 -2
- package/src/core/platform-process.ts +54 -6
- package/src/core/run.ts +20 -0
- package/src/core/usage.ts +8 -0
- package/src/drivers/claude-code/index.ts +34 -3
- package/src/drivers/codex/app-events.ts +43 -33
- package/src/drivers/codex/exec-events.ts +258 -0
- package/src/drivers/codex/index.ts +37 -262
- package/src/drivers/cursor/README.md +57 -0
- package/src/drivers/cursor/acp-transport.ts +182 -0
- package/src/drivers/cursor/config.ts +29 -0
- package/src/drivers/cursor/events.ts +83 -0
- package/src/drivers/cursor/index.ts +316 -0
- package/src/drivers/cursor/interaction.ts +90 -0
- package/src/drivers/mcp/index.ts +16 -3
- package/src/drivers/shared/experiment-behavior.ts +214 -0
- package/src/drivers/shared/experiment-policy.ts +195 -0
- package/src/drivers/subprocess/index.ts +45 -6
- package/src/index.ts +16 -5
- package/src/orchestrator/annotation-loader.ts +10 -0
- package/src/orchestrator/annotation-store.ts +24 -3
- package/src/orchestrator/campaign-lifecycle.ts +5 -1
- package/src/orchestrator/compare.ts +16 -1
- package/src/orchestrator/comparison-identity.ts +1 -0
- package/src/orchestrator/config.ts +6 -2
- package/src/orchestrator/discover.ts +8 -1
- package/src/orchestrator/doctor.ts +40 -22
- package/src/orchestrator/driver-capabilities.ts +16 -0
- package/src/orchestrator/driver-identity.ts +59 -0
- package/src/orchestrator/experiment-corpus.ts +23 -28
- package/src/orchestrator/experiment-effects.ts +61 -0
- package/src/orchestrator/experiment-evaluation.ts +45 -5
- package/src/orchestrator/experiment-identity.ts +27 -36
- package/src/orchestrator/experiment-report.ts +21 -1
- package/src/orchestrator/experiment-runs.ts +11 -4
- package/src/orchestrator/experiment-runtime-identity.ts +5 -243
- package/src/orchestrator/experiment-selection.ts +77 -0
- package/src/orchestrator/experiment.ts +26 -24
- package/src/orchestrator/fs-read.ts +2 -32
- package/src/orchestrator/grader-experiment.ts +72 -8
- package/src/orchestrator/grading.ts +15 -6
- package/src/orchestrator/list-runs.ts +29 -6
- package/src/orchestrator/optimization-lifecycle.ts +4 -1
- package/src/orchestrator/orchestrator.ts +26 -183
- package/src/orchestrator/paired-effects.ts +209 -0
- package/src/orchestrator/provenance.ts +23 -3
- package/src/orchestrator/run-execution.ts +18 -3
- package/src/orchestrator/run-lifecycle.ts +4 -2
- package/src/orchestrator/suite-runs.ts +10 -1
- package/src/orchestrator/suite-summary.ts +2 -1
- package/src/previewer/server.ts +27 -5
- package/viewer/app.js +27 -6
- package/viewer/detail.js +22 -5
- package/lib/answerers/scripted/index.d.ts +0 -18
- package/lib/answerers/scripted/index.d.ts.map +0 -1
- package/lib/answerers/scripted/index.js +0 -63
- package/lib/answerers/scripted/index.js.map +0 -1
- package/lib/cli/skills.d.ts +0 -38
- package/lib/cli/skills.d.ts.map +0 -1
- package/lib/cli/skills.js +0 -84
- package/lib/cli/skills.js.map +0 -1
- package/src/answerers/scripted/index.ts +0 -78
- package/src/cli/skills.ts +0 -129
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { basename, extname,
|
|
1
|
+
import { basename, extname, join, resolve } from "node:path";
|
|
2
|
+
import { type EnvironmentDeclaration, EnvironmentDeclarationSchema } from "../core/environment.js";
|
|
2
3
|
import {
|
|
3
4
|
type Answerer,
|
|
4
5
|
type AnswererModelCallAccounting,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
registryForEvalCase,
|
|
25
26
|
valuesForFixtures,
|
|
26
27
|
} from "./case-extensions.js";
|
|
28
|
+
import { validateDriverCapabilities } from "./driver-capabilities.js";
|
|
27
29
|
import { evaluatorImplementationHash } from "./evaluator-identity.js";
|
|
28
30
|
import {
|
|
29
31
|
assertionEvaluatorIdentities,
|
|
@@ -102,6 +104,7 @@ const recordLifecycleCleanupFailure = async (result: RunResult, error: unknown):
|
|
|
102
104
|
};
|
|
103
105
|
|
|
104
106
|
export type OrchestratorOptions = {
|
|
107
|
+
environment?: EnvironmentDeclaration | undefined;
|
|
105
108
|
registry: Registry;
|
|
106
109
|
/** Answerer used when a case names none. Defaults to "persona". */
|
|
107
110
|
defaultAnswerer?: AnswererRef;
|
|
@@ -114,6 +117,8 @@ export type OrchestratorOptions = {
|
|
|
114
117
|
};
|
|
115
118
|
|
|
116
119
|
export type RunRequest = {
|
|
120
|
+
/** Effective parent suite worker limit; direct runs default to one. */
|
|
121
|
+
suiteConcurrency?: number | undefined;
|
|
117
122
|
/** Path to a TS case file. Mutually exclusive with `case`. */
|
|
118
123
|
casePath?: string;
|
|
119
124
|
/** Pre-loaded EvalCase. Mutually exclusive with `casePath`. */
|
|
@@ -265,191 +270,22 @@ const driverConfigOf = (ref: DriverRef): unknown =>
|
|
|
265
270
|
const answererConfigOf = (ref: AnswererRef): unknown =>
|
|
266
271
|
typeof ref === "object" ? ref.config : undefined;
|
|
267
272
|
|
|
268
|
-
const EXPERIMENT_BEHAVIOR_SOURCE_ARGS = new Set([
|
|
269
|
-
"--continue",
|
|
270
|
-
"-c",
|
|
271
|
-
"--fork-session",
|
|
272
|
-
"--resume",
|
|
273
|
-
"-r",
|
|
274
|
-
"--add-dir",
|
|
275
|
-
"--append-system-prompt-file",
|
|
276
|
-
"--mcp-config",
|
|
277
|
-
"--plugin-dir",
|
|
278
|
-
"--settings",
|
|
279
|
-
"--setting-sources",
|
|
280
|
-
"--system-prompt-file",
|
|
281
|
-
]);
|
|
282
|
-
|
|
283
|
-
const CODEX_EXPERIMENT_SOURCE_ARGS = new Set([
|
|
284
|
-
"--add-dir",
|
|
285
|
-
"--cd",
|
|
286
|
-
"-C",
|
|
287
|
-
"--config",
|
|
288
|
-
"-c",
|
|
289
|
-
"--profile",
|
|
290
|
-
"-p",
|
|
291
|
-
]);
|
|
292
|
-
|
|
293
|
-
const bindSubprocessExperimentConfig = (config: Record<string, unknown>): unknown => {
|
|
294
|
-
if (typeof config.command !== "string" || !isAbsolute(config.command)) {
|
|
295
|
-
throw new Error(
|
|
296
|
-
"experiment subprocess commands must be absolute direct wrappers so Lux can fingerprint the invoked behavior",
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
if (typeof config.cwd === "string") {
|
|
300
|
-
throw new Error(
|
|
301
|
-
"experiment runs do not allow an explicit subprocess cwd; use the candidate subject and staged fixtures",
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
if (Array.isArray(config.args) && config.args.length > 0) {
|
|
305
|
-
throw new Error(
|
|
306
|
-
"experiment subprocesses do not allow args; put stable behavior in the fingerprinted direct wrapper",
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
if (isRecord(config.env) && Object.keys(config.env).length > 0) {
|
|
310
|
-
throw new Error(
|
|
311
|
-
"experiment subprocesses do not allow custom env; put stable behavior in the fingerprinted direct wrapper",
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
return { ...config, inheritEnv: false };
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
type AgentDriverName = "claude-code" | "codex";
|
|
318
|
-
|
|
319
|
-
const validateAgentExperimentConfig = (
|
|
320
|
-
driverName: AgentDriverName,
|
|
321
|
-
config: Record<string, unknown>,
|
|
322
|
-
): { driverEnv: Record<string, unknown>; extraArgs: string[] } => {
|
|
323
|
-
const driverEnv = isRecord(config.env) ? config.env : {};
|
|
324
|
-
const commandResolutionOverride = Object.keys(driverEnv).find((name) =>
|
|
325
|
-
["PATH", "PATHEXT"].includes(name.toUpperCase()),
|
|
326
|
-
);
|
|
327
|
-
if (commandResolutionOverride) {
|
|
328
|
-
throw new Error(
|
|
329
|
-
`experiment runs do not allow driver env.${commandResolutionOverride}; command resolution must match the fingerprinted host executable`,
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
if (typeof config.cwd === "string") {
|
|
333
|
-
throw new Error(
|
|
334
|
-
`experiment runs do not allow an explicit ${driverName} cwd; use the candidate subject and staged fixtures`,
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const extraArgs = Array.isArray(config.extraArgs)
|
|
339
|
-
? config.extraArgs.filter((value): value is string => typeof value === "string")
|
|
340
|
-
: [];
|
|
341
|
-
const disallowedArgs =
|
|
342
|
-
driverName === "claude-code" ? EXPERIMENT_BEHAVIOR_SOURCE_ARGS : CODEX_EXPERIMENT_SOURCE_ARGS;
|
|
343
|
-
const behaviorSourceArg = extraArgs.find((arg) =>
|
|
344
|
-
[...disallowedArgs].some((name) => arg === name || arg.startsWith(`${name}=`)),
|
|
345
|
-
);
|
|
346
|
-
if (behaviorSourceArg) {
|
|
347
|
-
throw new Error(
|
|
348
|
-
`experiment runs do not allow behavior-bearing extraArgs '${behaviorSourceArg}'; use structured driver fields so Lux can fingerprint the source`,
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const isolation = isRecord(config.isolation) ? config.isolation : {};
|
|
353
|
-
const structuredPaths = [
|
|
354
|
-
...(driverName === "claude-code" && Array.isArray(config.pluginDirs) ? config.pluginDirs : []),
|
|
355
|
-
...(Array.isArray(config.addDirs) ? config.addDirs : []),
|
|
356
|
-
...(driverName === "claude-code" && Array.isArray(isolation.allowRead)
|
|
357
|
-
? isolation.allowRead
|
|
358
|
-
: []),
|
|
359
|
-
].filter((value): value is string => typeof value === "string");
|
|
360
|
-
const relativeSource = structuredPaths.find((path) => !isAbsolute(path));
|
|
361
|
-
if (relativeSource) {
|
|
362
|
-
throw new Error(
|
|
363
|
-
`experiment driver source paths must be absolute after subjectPath binding: ${relativeSource}`,
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
return { driverEnv, extraArgs };
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
const bindCodexExperimentConfig = (
|
|
370
|
-
config: Record<string, unknown>,
|
|
371
|
-
extraArgs: readonly string[],
|
|
372
|
-
): unknown => {
|
|
373
|
-
if (typeof config.profile === "string") {
|
|
374
|
-
throw new Error(
|
|
375
|
-
"experiment runs do not allow a Codex profile; use inline driver config so Lux can fingerprint it",
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
if (config.ephemeral === false) {
|
|
379
|
-
throw new Error("experiment runs require ephemeral Codex sessions");
|
|
380
|
-
}
|
|
381
|
-
const inlineConfig = isRecord(config.config) ? config.config : {};
|
|
382
|
-
const externalConfigKey = ((): string | undefined => {
|
|
383
|
-
const visit = (value: Record<string, unknown>, parent = ""): string | undefined => {
|
|
384
|
-
for (const [key, nested] of Object.entries(value)) {
|
|
385
|
-
const path = parent ? `${parent}.${key}` : key;
|
|
386
|
-
if (/(?:mcp_servers|instructions_file|notify|project_doc)/i.test(key)) return path;
|
|
387
|
-
if (isRecord(nested)) {
|
|
388
|
-
const found = visit(nested, path);
|
|
389
|
-
if (found) return found;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
return undefined;
|
|
393
|
-
};
|
|
394
|
-
return visit(inlineConfig);
|
|
395
|
-
})();
|
|
396
|
-
if (externalConfigKey) {
|
|
397
|
-
throw new Error(
|
|
398
|
-
`experiment runs do not allow path-bearing Codex config '${externalConfigKey}'`,
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
const appServer = config.interactionMode !== "exec";
|
|
402
|
-
return {
|
|
403
|
-
...config,
|
|
404
|
-
ephemeral: true,
|
|
405
|
-
ignoreUserConfig: true,
|
|
406
|
-
ignoreRules: true,
|
|
407
|
-
...(appServer && !config.isolation ? { isolation: { allowRead: [] } } : {}),
|
|
408
|
-
extraArgs,
|
|
409
|
-
};
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
const bindClaudeExperimentConfig = (
|
|
413
|
-
config: Record<string, unknown>,
|
|
414
|
-
extraArgs: readonly string[],
|
|
415
|
-
driverEnv: Record<string, unknown>,
|
|
416
|
-
): unknown => {
|
|
417
|
-
if (config.isolation) return config;
|
|
418
|
-
return {
|
|
419
|
-
...config,
|
|
420
|
-
extraArgs: [
|
|
421
|
-
...extraArgs,
|
|
422
|
-
"--setting-sources",
|
|
423
|
-
"user",
|
|
424
|
-
"--strict-mcp-config",
|
|
425
|
-
"--settings",
|
|
426
|
-
JSON.stringify({ disableAllHooks: true }),
|
|
427
|
-
],
|
|
428
|
-
env: {
|
|
429
|
-
...driverEnv,
|
|
430
|
-
CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1",
|
|
431
|
-
CLAUDE_CODE_DISABLE_CLAUDE_MDS: "1",
|
|
432
|
-
CLAUDE_CODE_SKIP_PROMPT_HISTORY: "1",
|
|
433
|
-
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1",
|
|
434
|
-
CLAUDE_CODE_AUTO_CONNECT_IDE: "false",
|
|
435
|
-
CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL: "1",
|
|
436
|
-
DISABLE_AUTOUPDATER: "1",
|
|
437
|
-
},
|
|
438
|
-
};
|
|
439
|
-
};
|
|
440
|
-
|
|
441
273
|
export const boundExperimentDriverConfig = (
|
|
442
|
-
|
|
274
|
+
plugin: import("../core/driver.js").DriverPlugin,
|
|
443
275
|
config: unknown,
|
|
444
276
|
experimentId: string | undefined,
|
|
445
277
|
): unknown => {
|
|
446
|
-
if (!experimentId
|
|
447
|
-
if (
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
278
|
+
if (!experimentId) return config;
|
|
279
|
+
if (!plugin.experiment) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
`driver '${plugin.name}' supports ordinary eval runs but does not declare controlled experiment support`,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
return validatePluginConfig(
|
|
285
|
+
plugin.configSchema,
|
|
286
|
+
plugin.experiment.prepare(config),
|
|
287
|
+
`driver '${plugin.name}' experiment configuration`,
|
|
288
|
+
);
|
|
453
289
|
};
|
|
454
290
|
|
|
455
291
|
const closeQuietly = async (close: () => Promise<void>, label: string): Promise<void> => {
|
|
@@ -463,6 +299,7 @@ const closeQuietly = async (close: () => Promise<void>, label: string): Promise<
|
|
|
463
299
|
};
|
|
464
300
|
|
|
465
301
|
export class Orchestrator {
|
|
302
|
+
private readonly environment: EnvironmentDeclaration | undefined;
|
|
466
303
|
private evaluatorHash: Promise<string> | undefined;
|
|
467
304
|
readonly registry: Registry;
|
|
468
305
|
private readonly defaultAnswerer: AnswererRef;
|
|
@@ -471,6 +308,9 @@ export class Orchestrator {
|
|
|
471
308
|
private readonly lifecycleFixtures: readonly LifecycleHooksPlugin[];
|
|
472
309
|
|
|
473
310
|
constructor(options: OrchestratorOptions) {
|
|
311
|
+
this.environment = options.environment
|
|
312
|
+
? EnvironmentDeclarationSchema.parse(options.environment)
|
|
313
|
+
: undefined;
|
|
474
314
|
this.registry = options.registry;
|
|
475
315
|
this.defaultAnswerer = options.defaultAnswerer ?? "persona";
|
|
476
316
|
this.defaultDriver = options.defaultDriver;
|
|
@@ -534,7 +374,7 @@ export class Orchestrator {
|
|
|
534
374
|
// Validate both configs before instantiating either plugin so a
|
|
535
375
|
// schema failure can't leak a half-initialized driver/answerer.
|
|
536
376
|
const driverCfg = boundExperimentDriverConfig(
|
|
537
|
-
|
|
377
|
+
driverPlugin,
|
|
538
378
|
validatePluginConfig(
|
|
539
379
|
driverPlugin.configSchema,
|
|
540
380
|
driverConfigOf(driverRef),
|
|
@@ -548,6 +388,7 @@ export class Orchestrator {
|
|
|
548
388
|
`answerer '${answererName}'`,
|
|
549
389
|
);
|
|
550
390
|
|
|
391
|
+
validateDriverCapabilities(driverPlugin, driverCfg, evalCase.requires);
|
|
551
392
|
const driver = await driverPlugin.create(driverCfg);
|
|
552
393
|
let answerer: Answerer;
|
|
553
394
|
try {
|
|
@@ -599,6 +440,7 @@ export class Orchestrator {
|
|
|
599
440
|
skipJudge: request.skipJudge ?? false,
|
|
600
441
|
}),
|
|
601
442
|
fixtureValues: valuesForFixtures(lifecycleFixtures),
|
|
443
|
+
environment: this.environment,
|
|
602
444
|
driver,
|
|
603
445
|
answerer,
|
|
604
446
|
},
|
|
@@ -797,6 +639,7 @@ export class Orchestrator {
|
|
|
797
639
|
observer: handle ?? observer,
|
|
798
640
|
abortSignal: opts.abortSignal,
|
|
799
641
|
suiteId: suite.manifest.suiteId,
|
|
642
|
+
suiteConcurrency: concurrency,
|
|
800
643
|
subjectRoot: opts.subjectRoot,
|
|
801
644
|
subjectHash: opts.subjectHash,
|
|
802
645
|
profileId: opts.profileId,
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { clusterKey } from "../core/evaluation-clusters.js";
|
|
3
|
+
import {
|
|
4
|
+
bootstrapMeanConfidenceInterval,
|
|
5
|
+
type CandidateEvaluation,
|
|
6
|
+
type ConfidenceConfig,
|
|
7
|
+
ConfidenceIntervalSchema,
|
|
8
|
+
} from "../core/index.js";
|
|
9
|
+
|
|
10
|
+
type CaseSamples = NonNullable<CandidateEvaluation["caseSamples"]>;
|
|
11
|
+
type Evaluation = CandidateEvaluation & { repetitions: number };
|
|
12
|
+
|
|
13
|
+
export const clusterCaseSamples = (
|
|
14
|
+
groups: readonly (CaseSamples | undefined)[],
|
|
15
|
+
): CaseSamples | undefined => {
|
|
16
|
+
if (groups.some((group) => group === undefined)) return undefined;
|
|
17
|
+
const result = new Map<string, CaseSamples[string]>();
|
|
18
|
+
for (const group of groups) {
|
|
19
|
+
for (const [id, sample] of Object.entries(group ?? {})) {
|
|
20
|
+
const current = result.get(id);
|
|
21
|
+
if (!current) {
|
|
22
|
+
result.set(id, structuredClone(sample));
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (current.identityHash !== sample.identityHash) current.identityHash = null;
|
|
26
|
+
if (current.clusterId !== sample.clusterId) current.identityHash = null;
|
|
27
|
+
const metrics = new Map(Object.entries(current.metrics));
|
|
28
|
+
for (const [metric, value] of Object.entries(sample.metrics)) {
|
|
29
|
+
const before = metrics.get(metric);
|
|
30
|
+
metrics.set(
|
|
31
|
+
metric,
|
|
32
|
+
before
|
|
33
|
+
? {
|
|
34
|
+
mean: (before.mean * before.n + value.mean * value.n) / (before.n + value.n),
|
|
35
|
+
n: before.n + value.n,
|
|
36
|
+
}
|
|
37
|
+
: { ...value },
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
current.metrics = Object.fromEntries(metrics);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return Object.fromEntries(result);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const caseGroups = (samples: CaseSamples): Map<string, string[]> => {
|
|
47
|
+
const groups = new Map<string, string[]>();
|
|
48
|
+
for (const id of Object.keys(samples).sort()) {
|
|
49
|
+
const key = clusterKey(id, samples[id]?.clusterId);
|
|
50
|
+
groups.set(key, [...(groups.get(key) ?? []), id]);
|
|
51
|
+
}
|
|
52
|
+
return groups;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const groupedCaseMetricSamples = (
|
|
56
|
+
samples: CaseSamples | undefined,
|
|
57
|
+
repetitions: number,
|
|
58
|
+
): Record<string, number[]> | undefined => {
|
|
59
|
+
if (!samples || !Object.values(samples).some((sample) => sample.clusterId !== undefined))
|
|
60
|
+
return undefined;
|
|
61
|
+
const groups = caseGroups(samples);
|
|
62
|
+
const metrics = new Set(Object.values(samples).flatMap((sample) => Object.keys(sample.metrics)));
|
|
63
|
+
return Object.fromEntries(
|
|
64
|
+
[...metrics].flatMap((metric) => {
|
|
65
|
+
if (
|
|
66
|
+
Object.values(samples).some(
|
|
67
|
+
(sample) =>
|
|
68
|
+
!Object.hasOwn(sample.metrics, metric) || sample.metrics[metric]?.n !== repetitions,
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
return [];
|
|
72
|
+
return [
|
|
73
|
+
[
|
|
74
|
+
metric,
|
|
75
|
+
[...groups.values()].map(
|
|
76
|
+
(ids) =>
|
|
77
|
+
ids.reduce((sum, id) => sum + (samples[id]?.metrics[metric]?.mean ?? 0), 0) /
|
|
78
|
+
ids.length,
|
|
79
|
+
),
|
|
80
|
+
],
|
|
81
|
+
];
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const PairedEffectSchema = z.object({
|
|
87
|
+
caseIds: z.array(z.string()),
|
|
88
|
+
n: z.number().int().nonnegative(),
|
|
89
|
+
caseCount: z.number().int().nonnegative().optional(),
|
|
90
|
+
clusters: z.record(z.string(), z.array(z.string())).optional(),
|
|
91
|
+
meanDelta: z.number().finite().nullable(),
|
|
92
|
+
interval: ConfidenceIntervalSchema.nullable(),
|
|
93
|
+
note: z.string().nullable(),
|
|
94
|
+
});
|
|
95
|
+
export type PairedEffect = z.infer<typeof PairedEffectSchema>;
|
|
96
|
+
|
|
97
|
+
export const confidenceGateIssue = (
|
|
98
|
+
interval: z.infer<typeof ConfidenceIntervalSchema>,
|
|
99
|
+
): string | undefined =>
|
|
100
|
+
interval.method === "percentile-bootstrap" && interval.lower === interval.upper
|
|
101
|
+
? "zero-width percentile-bootstrap interval cannot establish promotion confidence; use Hoeffding for bounded means or collect more varied independent evidence"
|
|
102
|
+
: undefined;
|
|
103
|
+
|
|
104
|
+
const pairedDeltas = (
|
|
105
|
+
before: Evaluation | undefined,
|
|
106
|
+
after: Evaluation | undefined,
|
|
107
|
+
metric: string,
|
|
108
|
+
): { caseIds: string[]; values: number[]; clusters: Record<string, string[]> } => {
|
|
109
|
+
if (!before?.caseSamples || !after?.caseSamples)
|
|
110
|
+
throw new Error("keyed case evidence unavailable");
|
|
111
|
+
if (
|
|
112
|
+
before.profileId !== after.profileId ||
|
|
113
|
+
before.split !== after.split ||
|
|
114
|
+
before.repetitions !== after.repetitions
|
|
115
|
+
) {
|
|
116
|
+
throw new Error("paired evidence requires matching profile, split, and repetitions");
|
|
117
|
+
}
|
|
118
|
+
const caseIds = Object.keys(before.caseSamples).sort();
|
|
119
|
+
const afterIds = Object.keys(after.caseSamples).sort();
|
|
120
|
+
const beforeCases = Object.keys(before.casePassRates).sort();
|
|
121
|
+
const afterCases = Object.keys(after.casePassRates).sort();
|
|
122
|
+
if (
|
|
123
|
+
caseIds.length === 0 ||
|
|
124
|
+
[afterIds, beforeCases, afterCases].some(
|
|
125
|
+
(ids) => JSON.stringify(ids) !== JSON.stringify(caseIds),
|
|
126
|
+
)
|
|
127
|
+
) {
|
|
128
|
+
throw new Error("paired evidence requires identical canonical case sets");
|
|
129
|
+
}
|
|
130
|
+
const values = caseIds.map((id) => {
|
|
131
|
+
const a = before.caseSamples?.[id];
|
|
132
|
+
const b = after.caseSamples?.[id];
|
|
133
|
+
if (!a?.identityHash || a.identityHash !== b?.identityHash)
|
|
134
|
+
throw new Error(`paired case '${id}' has incompatible evaluation identity`);
|
|
135
|
+
if (a.clusterId !== b.clusterId)
|
|
136
|
+
throw new Error(`paired case '${id}' has incompatible cluster membership`);
|
|
137
|
+
const av = Object.hasOwn(a.metrics, metric) ? a.metrics[metric] : undefined;
|
|
138
|
+
const bv = Object.hasOwn(b.metrics, metric) ? b.metrics[metric] : undefined;
|
|
139
|
+
if (!av || !bv || av.n !== before.repetitions || bv.n !== after.repetitions) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`paired metric '${metric}' lacks complete repetition coverage for case '${id}'`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return bv.mean - av.mean;
|
|
145
|
+
});
|
|
146
|
+
const byCase = new Map(caseIds.map((id, index) => [id, values[index] ?? 0]));
|
|
147
|
+
const groups = caseGroups(before.caseSamples);
|
|
148
|
+
return {
|
|
149
|
+
caseIds,
|
|
150
|
+
clusters: Object.fromEntries(groups),
|
|
151
|
+
values: [...groups.values()].map(
|
|
152
|
+
(ids) => ids.reduce((sum, id) => sum + (byCase.get(id) ?? 0), 0) / ids.length,
|
|
153
|
+
),
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const pairedEffect = (
|
|
158
|
+
before: Evaluation | undefined,
|
|
159
|
+
after: Evaluation | undefined,
|
|
160
|
+
metric: string,
|
|
161
|
+
confidence?: ConfidenceConfig,
|
|
162
|
+
): PairedEffect => {
|
|
163
|
+
let paired: ReturnType<typeof pairedDeltas>;
|
|
164
|
+
try {
|
|
165
|
+
paired = pairedDeltas(before, after, metric);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
caseIds: [],
|
|
169
|
+
n: 0,
|
|
170
|
+
caseCount: 0,
|
|
171
|
+
clusters: {},
|
|
172
|
+
meanDelta: null,
|
|
173
|
+
interval: null,
|
|
174
|
+
note: error instanceof Error ? error.message : String(error),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const result: PairedEffect = {
|
|
178
|
+
caseIds: paired.caseIds,
|
|
179
|
+
n: paired.values.length,
|
|
180
|
+
caseCount: paired.caseIds.length,
|
|
181
|
+
clusters: paired.clusters,
|
|
182
|
+
meanDelta: paired.values.reduce((sum, value) => sum + value, 0) / paired.values.length,
|
|
183
|
+
interval: null,
|
|
184
|
+
note: null,
|
|
185
|
+
};
|
|
186
|
+
if (!confidence) return result;
|
|
187
|
+
try {
|
|
188
|
+
if (confidence.method === "wilson")
|
|
189
|
+
throw new Error("Wilson cannot estimate paired differences");
|
|
190
|
+
if (confidence.method === "hoeffding") {
|
|
191
|
+
const interval = bootstrapMeanConfidenceInterval(
|
|
192
|
+
paired.values.map((value) => (value + 1) / 2),
|
|
193
|
+
confidence,
|
|
194
|
+
);
|
|
195
|
+
result.interval = {
|
|
196
|
+
...interval,
|
|
197
|
+
estimate: 2 * interval.estimate - 1,
|
|
198
|
+
lower: 2 * interval.lower - 1,
|
|
199
|
+
upper: 2 * interval.upper - 1,
|
|
200
|
+
};
|
|
201
|
+
} else {
|
|
202
|
+
result.interval = bootstrapMeanConfidenceInterval(paired.values, confidence);
|
|
203
|
+
}
|
|
204
|
+
result.note = confidenceGateIssue(result.interval) ?? null;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
result.note = error instanceof Error ? error.message : String(error);
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { lstat, readFile, readlink } from "node:fs/promises";
|
|
3
|
+
import { availableParallelism, cpus, release, totalmem } from "node:os";
|
|
3
4
|
import { dirname, join, parse, relative, resolve, sep } from "node:path";
|
|
5
|
+
import type { EnvironmentDeclaration } from "../core/environment.js";
|
|
4
6
|
import type { RunProvenance } from "../core/index.js";
|
|
5
7
|
import { contentHash, exec, valueHash } from "../core/index.js";
|
|
6
8
|
|
|
@@ -113,13 +115,30 @@ export const runtimeIdentity = (): RunProvenance["runtime"] => ({
|
|
|
113
115
|
arch: process.arch,
|
|
114
116
|
node: process.version,
|
|
115
117
|
...(process.versions.bun ? { bun: process.versions.bun } : {}),
|
|
118
|
+
host: {
|
|
119
|
+
osRelease: release(),
|
|
120
|
+
cpuModels: [...new Set(cpus().map((cpu) => cpu.model))].sort(),
|
|
121
|
+
logicalCpuCount: cpus().length,
|
|
122
|
+
availableParallelism: availableParallelism(),
|
|
123
|
+
totalMemoryBytes: totalmem(),
|
|
124
|
+
},
|
|
116
125
|
});
|
|
117
126
|
|
|
118
|
-
export const runProvenance = async (
|
|
127
|
+
export const runProvenance = async (
|
|
128
|
+
casePath: string | null,
|
|
129
|
+
options: {
|
|
130
|
+
environment?: EnvironmentDeclaration | undefined;
|
|
131
|
+
concurrency?: number | undefined;
|
|
132
|
+
} = {},
|
|
133
|
+
): Promise<RunProvenance> => {
|
|
119
134
|
const runtime = runtimeIdentity();
|
|
120
|
-
|
|
135
|
+
const environment = {
|
|
136
|
+
...(options.environment ? { declared: options.environment } : {}),
|
|
137
|
+
concurrency: options.concurrency ?? 1,
|
|
138
|
+
};
|
|
139
|
+
if (!casePath) return { runtime, environment };
|
|
121
140
|
const root = gitRootAbove(casePath);
|
|
122
|
-
if (!root) return { runtime, source: { casePath: resolve(casePath) } };
|
|
141
|
+
if (!root) return { runtime, environment, source: { casePath: resolve(casePath) } };
|
|
123
142
|
let git = repositories.get(root);
|
|
124
143
|
if (!git) {
|
|
125
144
|
git = inspectRepository(root);
|
|
@@ -128,6 +147,7 @@ export const runProvenance = async (casePath: string | null): Promise<RunProvena
|
|
|
128
147
|
const state = await git;
|
|
129
148
|
return {
|
|
130
149
|
runtime,
|
|
150
|
+
environment,
|
|
131
151
|
source: {
|
|
132
152
|
casePath: portablePath(relative(root, resolve(casePath))),
|
|
133
153
|
...(state ? { git: state } : {}),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cp } from "node:fs/promises";
|
|
2
2
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import type { EnvironmentDeclaration } from "../core/environment.js";
|
|
4
5
|
import {
|
|
5
6
|
type AgentDriver,
|
|
6
7
|
type Answerer,
|
|
@@ -60,6 +61,7 @@ export const INLINE_CASE_PATH = "<inline>";
|
|
|
60
61
|
type RunDirCarrier = { luxRunDir?: string };
|
|
61
62
|
type InterruptCounts = { ask: number; approve: number; custom: number };
|
|
62
63
|
export type RunWithPluginsArgs = {
|
|
64
|
+
environment?: EnvironmentDeclaration | undefined;
|
|
63
65
|
request: RunRequest;
|
|
64
66
|
casePath: string;
|
|
65
67
|
authoredCase: EvalCase;
|
|
@@ -173,8 +175,17 @@ const SummaryModelAccountingSchema = z.object({
|
|
|
173
175
|
unpricedModelCalls: z.number().int().nonnegative().optional(),
|
|
174
176
|
});
|
|
175
177
|
|
|
176
|
-
const usageFromSummary = (summary: unknown): TokenUsage | null =>
|
|
177
|
-
|
|
178
|
+
const usageFromSummary = (summary: unknown): TokenUsage | null => {
|
|
179
|
+
if (!summary || typeof summary !== "object") return null;
|
|
180
|
+
const raw = (summary as { usage?: unknown }).usage;
|
|
181
|
+
if (!raw || typeof raw !== "object") return null;
|
|
182
|
+
const counts = raw as Record<string, unknown>;
|
|
183
|
+
const hasCount = ["input", "output", "cacheRead", "cacheCreation"].some((key) => {
|
|
184
|
+
const value = counts[key];
|
|
185
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
186
|
+
});
|
|
187
|
+
return hasCount ? (SummaryUsageSchema.safeParse(summary).data?.usage ?? null) : null;
|
|
188
|
+
};
|
|
178
189
|
|
|
179
190
|
const costFromSummary = (summary: unknown): number | undefined => {
|
|
180
191
|
if (!summary || typeof summary !== "object") return undefined;
|
|
@@ -320,6 +331,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
320
331
|
const startedAt = args.request.startedAt ?? new Date();
|
|
321
332
|
const provenance = await runProvenance(
|
|
322
333
|
args.casePath === INLINE_CASE_PATH ? null : args.casePath,
|
|
334
|
+
{ environment: args.environment, concurrency: args.request.suiteConcurrency },
|
|
323
335
|
);
|
|
324
336
|
const observer = guardRunObserver(args.request.observer);
|
|
325
337
|
const runDir = await openRunDir({
|
|
@@ -528,6 +540,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
528
540
|
exitReason: "error",
|
|
529
541
|
driver: { name: this.args.driverName, ...ready },
|
|
530
542
|
answerer: answererMetadata(this.args.answererName, this.args.answererConfig),
|
|
543
|
+
agentUsageAvailability: "unavailable",
|
|
531
544
|
usage: {
|
|
532
545
|
agent: emptyUsage(),
|
|
533
546
|
answerer: readAnswererUsage(this.args.answerer),
|
|
@@ -580,8 +593,9 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
580
593
|
const answererAccounting = readAnswererModelAccounting(this.args.answerer);
|
|
581
594
|
const agentCalls = this.ready ? (reportedModelAccounting?.modelCalls ?? 1) : 0;
|
|
582
595
|
const operationalFailure = loopFailure(outcome, this.collection.error);
|
|
596
|
+
const agentUsage = usageFromSummary(summary);
|
|
583
597
|
const usage: RunUsage = {
|
|
584
|
-
agent:
|
|
598
|
+
agent: agentUsage ?? emptyUsage(),
|
|
585
599
|
answerer: readAnswererUsage(this.args.answerer),
|
|
586
600
|
grading: emptyUsage(),
|
|
587
601
|
};
|
|
@@ -597,6 +611,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
597
611
|
driver: { name: this.args.driverName, ...ready },
|
|
598
612
|
answerer: answererMetadata(this.args.answererName, this.args.answererConfig),
|
|
599
613
|
usage,
|
|
614
|
+
agentUsageAvailability: agentUsage === null ? "unavailable" : "reported",
|
|
600
615
|
modelAccounting: {
|
|
601
616
|
agentCalls,
|
|
602
617
|
answererCalls: answererAccounting.calls,
|
|
@@ -23,6 +23,7 @@ type RunLifecycleContext = {
|
|
|
23
23
|
phase: RunFailurePhase;
|
|
24
24
|
result: unknown;
|
|
25
25
|
error: unknown;
|
|
26
|
+
failed: boolean;
|
|
26
27
|
};
|
|
27
28
|
type RunLifecycleInput = { effects: ErasedEffects };
|
|
28
29
|
type RunLifecycleOutcome =
|
|
@@ -56,7 +57,7 @@ const runLifecycleMachine = setup({
|
|
|
56
57
|
actions: {
|
|
57
58
|
captureError: assign(({ context, event }) => {
|
|
58
59
|
const error = "error" in event ? event.error : new Error("run lifecycle failed");
|
|
59
|
-
return { error, phase: isRunTraceLimit(error) ? "trace" : context.phase };
|
|
60
|
+
return { error, failed: true, phase: isRunTraceLimit(error) ? "trace" : context.phase };
|
|
60
61
|
}),
|
|
61
62
|
captureResult: assign({
|
|
62
63
|
result: ({ event }) => ("output" in event ? event.output : undefined),
|
|
@@ -73,6 +74,7 @@ const runLifecycleMachine = setup({
|
|
|
73
74
|
phase: "setup",
|
|
74
75
|
result: undefined,
|
|
75
76
|
error: undefined,
|
|
77
|
+
failed: false,
|
|
76
78
|
}),
|
|
77
79
|
initial: "preparing",
|
|
78
80
|
states: {
|
|
@@ -153,7 +155,7 @@ const runLifecycleMachine = setup({
|
|
|
153
155
|
failed: { type: "final" },
|
|
154
156
|
},
|
|
155
157
|
output: ({ context }) => {
|
|
156
|
-
if (context.
|
|
158
|
+
if (context.failed) {
|
|
157
159
|
return { status: "failed", error: context.error };
|
|
158
160
|
}
|
|
159
161
|
return { status: "succeeded", result: context.result };
|
|
@@ -126,6 +126,7 @@ export const loadSuiteRun = async (path: string): Promise<SuiteRun | null> =>
|
|
|
126
126
|
|
|
127
127
|
export const listSuiteRuns = async (
|
|
128
128
|
runsRoot: string,
|
|
129
|
+
options: { onReadError?: (path: string, error: unknown) => void } = {},
|
|
129
130
|
): Promise<Array<SuiteRun & { suiteDir: string }>> => {
|
|
130
131
|
const root = join(runsRoot, SUITES_DIR);
|
|
131
132
|
if (!existsSync(root)) return [];
|
|
@@ -133,7 +134,15 @@ export const listSuiteRuns = async (
|
|
|
133
134
|
.filter((entry) => entry.isDirectory())
|
|
134
135
|
.map((entry) => join(root, entry.name));
|
|
135
136
|
const manifests = await Promise.all(
|
|
136
|
-
dirs.map(async (suiteDir) =>
|
|
137
|
+
dirs.map(async (suiteDir) => {
|
|
138
|
+
try {
|
|
139
|
+
return { suiteDir, suite: await loadSuiteRun(suiteDir) };
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (!options.onReadError) throw error;
|
|
142
|
+
options.onReadError(suiteDir, error);
|
|
143
|
+
return { suiteDir, suite: null };
|
|
144
|
+
}
|
|
145
|
+
}),
|
|
137
146
|
);
|
|
138
147
|
return manifests
|
|
139
148
|
.flatMap(({ suiteDir, suite }) => (suite ? [{ ...suite, suiteDir }] : []))
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
isUsageEmpty,
|
|
9
9
|
type RunMetadata,
|
|
10
10
|
} from "../core/index.js";
|
|
11
|
+
import { hasAgentUsage } from "../core/usage.js";
|
|
11
12
|
import { runComparisonIdentity } from "./comparison-identity.js";
|
|
12
13
|
import { safeReadJson } from "./fs-read.js";
|
|
13
14
|
import type { SuiteCaseResult } from "./orchestrator.js";
|
|
@@ -102,7 +103,7 @@ const collectMetadata = (samples: MetricSamples, metadata: RunMetadata): void =>
|
|
|
102
103
|
add(samples, "durationMs", Math.max(0, ended - started));
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
if (
|
|
106
|
+
if (hasAgentUsage(metadata)) {
|
|
106
107
|
add(samples, "agent.input", metadata.usage.agent.input);
|
|
107
108
|
add(samples, "agent.output", metadata.usage.agent.output);
|
|
108
109
|
add(samples, "agent.cacheRead", metadata.usage.agent.cacheRead);
|