@workos/quickstudy 0.11.0 → 0.12.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.
- package/images/agent-runtime/Dockerfile +1 -1
- package/package.json +1 -1
- package/src/build-info.generated.ts +1 -1
- package/src/evals/types.ts +5 -3
- package/src/execution-policy.ts +17 -3
- package/src/experiments/types.ts +14 -0
- package/src/isolation/image-contexts.generated.ts +1 -1
- package/src/report/aggregate.ts +2 -2
- package/src/runner/execute.ts +7 -8
- package/ui/dist/index.html +10 -10
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
# Framework toolchains (Rails, Django, ...) layer on top in Phase 3 — this
|
|
10
10
|
# image stays agent-only.
|
|
11
11
|
|
|
12
|
-
FROM node:22-bookworm-slim
|
|
12
|
+
FROM node:22.23.4-bookworm-slim@sha256:de170b39439eb0e5a3444ad7d60b015614a381e10fecae2e30ec9e735122f38d
|
|
13
13
|
|
|
14
14
|
ARG CLAUDE_CODE_VERSION=2.1.215
|
|
15
15
|
ARG CODEX_VERSION=0.149.1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workos/quickstudy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "An open-source, company-agnostic eval harness for measuring whether coding agents can complete real integration tasks, across an agent x feature x framework x surface matrix.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -9,7 +9,7 @@ export const EMBEDDED_BUILD_INFO: {
|
|
|
9
9
|
// (the `x-release-please-version` marker anchors the replacement). It is
|
|
10
10
|
// restored as-is after a binary build (scripts/build-binary.ts), so the
|
|
11
11
|
// committed source `--version` stays in lockstep with package.json.
|
|
12
|
-
version: "0.
|
|
12
|
+
version: "0.12.1", // x-release-please-version
|
|
13
13
|
revision: null,
|
|
14
14
|
dirty: null,
|
|
15
15
|
sourceHash: null,
|
package/src/evals/types.ts
CHANGED
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* Closed set of eval suites. `benchmark` and `regression` are the headline
|
|
10
10
|
* suites: the leaderboard aggregates count both. `smoke` holds evals every
|
|
11
|
-
* arm solves — run to catch breakage, never moving the headline. `
|
|
12
|
-
*
|
|
11
|
+
* arm solves — run to catch breakage, never moving the headline. `discovery`
|
|
12
|
+
* holds vendor-neutral tasks whose outcome is categorical (which product was
|
|
13
|
+
* chosen) rather than pass/fail; recorded per attempt, never headline.
|
|
14
|
+
* `other` is anything else outside the headline.
|
|
13
15
|
*/
|
|
14
|
-
export const EVAL_SUITES = ["benchmark", "regression", "smoke", "other"] as const;
|
|
16
|
+
export const EVAL_SUITES = ["benchmark", "regression", "smoke", "other", "discovery"] as const;
|
|
15
17
|
export type EvalSuite = (typeof EVAL_SUITES)[number];
|
|
16
18
|
|
|
17
19
|
/** The suites whose attempts count toward the leaderboard aggregates. */
|
package/src/execution-policy.ts
CHANGED
|
@@ -53,6 +53,13 @@ export function resolvePairEnvironments(options: {
|
|
|
53
53
|
images?: Record<string, RuntimeImageIdentity>;
|
|
54
54
|
egressProxy?: boolean;
|
|
55
55
|
adapterHosts?: Record<string, readonly string[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Per experiment: the hosts of the agent's endpoint overrides. They join
|
|
58
|
+
* the allowlist only for pairs the proxy fronts, where the list is live and
|
|
59
|
+
* an IP-literal gateway is refused loudly by design; an open or unproxied
|
|
60
|
+
* pair never carries them, so such a gateway cannot stop the run.
|
|
61
|
+
*/
|
|
62
|
+
adapterEndpointHosts?: Record<string, readonly string[]>;
|
|
56
63
|
/** Per experiment: the agent's non-secret endpoint overrides (see PairEnvironment.endpoints). */
|
|
57
64
|
adapterEndpoints?: Record<string, Record<string, string>>;
|
|
58
65
|
budgets?: Partial<LifecycleBudgets>;
|
|
@@ -73,16 +80,23 @@ export function resolvePairEnvironments(options: {
|
|
|
73
80
|
options.imageTags?.[pairKey(loaded.metadata.id, experiment.id)] ??
|
|
74
81
|
runtime.image ??
|
|
75
82
|
runtime.containerImage?.(loaded.metadata);
|
|
83
|
+
// With the flag, every container is proxied unless its runtime opts
|
|
84
|
+
// out with egress: "open". No runtime hosts means only the adapter's
|
|
85
|
+
// provider endpoints are permitted. An open pair still records the
|
|
86
|
+
// runtime and adapter hosts (inert) so identity holds if the run flag
|
|
87
|
+
// flips, but endpoint-override hosts join only a live allowlist: the
|
|
88
|
+
// proxy refuses IP literals, and an IP-literal gateway on an arm the
|
|
89
|
+
// proxy never fronts must not stop the run.
|
|
90
|
+
const proxied = runtime.kind === "container" && (options.egressProxy ?? false) && runtime.egress !== "open";
|
|
76
91
|
pairs[pairKey(loaded.metadata.id, experiment.id)] = {
|
|
77
92
|
...(files ? { workspaceFiles: workspaceFilesIdentity(files) } : {}),
|
|
78
93
|
image: tag ? (options.images?.[tag] ?? { tag, id: "unresolved", repoDigests: [] }) : null,
|
|
79
|
-
// With the flag, every container is proxied. No runtime hosts means
|
|
80
|
-
// only the adapter's provider endpoints are permitted.
|
|
81
94
|
egress: {
|
|
82
|
-
proxy:
|
|
95
|
+
proxy: proxied,
|
|
83
96
|
allowlist: composeAllowlistFromHosts([
|
|
84
97
|
...(runtime.egressHosts?.(loaded.metadata) ?? []),
|
|
85
98
|
...(options.adapterHosts?.[experiment.id] ?? []),
|
|
99
|
+
...(proxied ? (options.adapterEndpointHosts?.[experiment.id] ?? []) : []),
|
|
86
100
|
]),
|
|
87
101
|
},
|
|
88
102
|
webPolicy: runtime.webPolicy?.() ?? "native-web-blocked",
|
package/src/experiments/types.ts
CHANGED
|
@@ -55,6 +55,15 @@ export interface Runtime extends RuntimeCapabilities {
|
|
|
55
55
|
* declare the same fields. Source/config identity still changes per attempt.
|
|
56
56
|
*/
|
|
57
57
|
treatmentPolicyFields?: Array<"egress" | "webPolicy" | "pathTreatment" | "mcp" | "promptPreamble" | "workspaceFiles">;
|
|
58
|
+
/**
|
|
59
|
+
* Whether this experiment's attempts go through the run's egress proxy.
|
|
60
|
+
* "open" skips the proxy sidecar and network for these attempts even when
|
|
61
|
+
* the run was started with --egress-proxy, so an arm that must reach the
|
|
62
|
+
* open internet can share a run with proxied arms. The choice is recorded
|
|
63
|
+
* in each pair environment (identity); exempt it in a comparison group with
|
|
64
|
+
* treatmentPolicyFields: ["egress"]. Container runtimes only.
|
|
65
|
+
*/
|
|
66
|
+
egress?: "proxied" | "open";
|
|
58
67
|
/** Attempt-local non-secret files, staged without replacing the normal adapter. */
|
|
59
68
|
workspaceFiles?: WorkspaceFiles;
|
|
60
69
|
/** Container image tag (container runtimes; alternative to `containerImage`). */
|
|
@@ -127,6 +136,11 @@ export function defineExperiment(experiment: Experiment): Experiment {
|
|
|
127
136
|
if (experiment.comparisonGroup !== undefined && experiment.comparisonGroup.trim() === "") {
|
|
128
137
|
throw new Error(`defineExperiment: experiment "${experiment.id}" comparisonGroup must not be empty`);
|
|
129
138
|
}
|
|
139
|
+
if (kind === "host" && experiment.runtime.egress === "open") {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`defineExperiment: experiment "${experiment.id}" runtime.egress "open" is only meaningful on container runtimes`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
130
144
|
validateWorkspaceFilesRuntime(experiment.runtime);
|
|
131
145
|
return deepFreeze(experiment);
|
|
132
146
|
}
|
|
@@ -23,7 +23,7 @@ export const IMAGE_CONTEXT_FILES: Record<string, string> = {
|
|
|
23
23
|
# Framework toolchains (Rails, Django, ...) layer on top in Phase 3 — this
|
|
24
24
|
# image stays agent-only.
|
|
25
25
|
|
|
26
|
-
FROM node:22-bookworm-slim
|
|
26
|
+
FROM node:22.23.4-bookworm-slim@sha256:de170b39439eb0e5a3444ad7d60b015614a381e10fecae2e30ec9e735122f38d
|
|
27
27
|
|
|
28
28
|
ARG CLAUDE_CODE_VERSION=2.1.215
|
|
29
29
|
ARG CODEX_VERSION=0.149.1
|
package/src/report/aggregate.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* the same attempts and (banded) pairs as the rest of the report.
|
|
4
4
|
*
|
|
5
5
|
* Every number is restricted to headline-suite attempts (HEADLINE_SUITES:
|
|
6
|
-
* benchmark and regression), so smoke and other suites never
|
|
7
|
-
* headline row. Telemetry follows the report's rule: sums and means cover
|
|
6
|
+
* benchmark and regression), so the smoke, discovery and other suites never
|
|
7
|
+
* move the headline row. Telemetry follows the report's rule: sums and means cover
|
|
8
8
|
* only attempts that reported a value, and are null (never 0) when none did.
|
|
9
9
|
*/
|
|
10
10
|
import { isHeadlineSuite } from "../evals/types.ts";
|
package/src/runner/execute.ts
CHANGED
|
@@ -471,20 +471,19 @@ export async function executeRunV3(options: ExecuteRunV3Options): Promise<RunSum
|
|
|
471
471
|
if (scorer.identity) scorerIdentities[loaded.metadata.id] = scorer.identity;
|
|
472
472
|
}
|
|
473
473
|
// Each agent's endpoint overrides set on the host: the non-secret base
|
|
474
|
-
// URLs join the pair environment (identity)
|
|
475
|
-
// allowlist
|
|
476
|
-
//
|
|
477
|
-
//
|
|
474
|
+
// URLs join the pair environment (identity); their hosts reach the egress
|
|
475
|
+
// allowlist only for pairs the proxy fronts (pair resolution decides: the
|
|
476
|
+
// run flag on and the runtime not opted out), where the list is live and
|
|
477
|
+
// an IP-literal gateway is refused loudly by design. Elsewhere the list
|
|
478
|
+
// is inert and an IP-literal override must not stop the run.
|
|
478
479
|
const overridesByExperiment = new Map([...containerAgents].map(([id, adapter]) => [id, collectOptionalHostEnv([adapter])] as const));
|
|
479
480
|
const adapterEndpoints = Object.fromEntries([...overridesByExperiment].flatMap(([id, overrides]) => {
|
|
480
481
|
const endpoints = Object.fromEntries(Object.entries(overrides).filter(([name]) => !isSecretEnvName(name)));
|
|
481
482
|
return Object.keys(endpoints).length > 0 ? [[id, endpoints] as const] : [];
|
|
482
483
|
}));
|
|
483
484
|
const pairs = resolvePairEnvironments({ ...options, workspaceFilesByExperiment, images, imageTags: imageTagsByPair,
|
|
484
|
-
adapterHosts: Object.fromEntries([...containerAgents].map(([id, adapter]) => [id, [
|
|
485
|
-
|
|
486
|
-
...(options.egressProxy ? endpointHosts(overridesByExperiment.get(id) ?? {}) : []),
|
|
487
|
-
]])),
|
|
485
|
+
adapterHosts: Object.fromEntries([...containerAgents].map(([id, adapter]) => [id, [...(adapter.egressHosts ?? [])]])),
|
|
486
|
+
adapterEndpointHosts: Object.fromEntries([...overridesByExperiment].map(([id, overrides]) => [id, endpointHosts(overrides)])),
|
|
488
487
|
adapterEndpoints,
|
|
489
488
|
budgets: { ...options.budgets, agentMs: options.attemptTimeoutMs ?? options.budgets?.agentMs ?? attemptTimeoutMs },
|
|
490
489
|
mcpHashes: Object.fromEntries([...declaredByExperiment].map(([id, servers]) => [id, hashString(canonicalJson(servers))])),
|