@piagent/platform 1.2.11 → 1.2.12
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/CHANGELOG.md +37 -0
- package/README.md +9 -7
- package/benchmarks/production-v1/grade.mjs +220 -0
- package/benchmarks/production-v1/project/README.md +7 -0
- package/benchmarks/production-v1/project/config/service.json +4 -0
- package/benchmarks/production-v1/project/data/audit-ledger.jsonl +1 -0
- package/benchmarks/production-v1/project/docs/ops.md +3 -0
- package/benchmarks/production-v1/project/logs/incident.log +2 -0
- package/benchmarks/production-v1/project/package.json +11 -0
- package/benchmarks/production-v1/project/scripts/check.mjs +21 -0
- package/benchmarks/production-v1/project/src/backend/auth.js +3 -0
- package/benchmarks/production-v1/project/src/backend/cache.js +15 -0
- package/benchmarks/production-v1/project/src/backend/invoice.js +4 -0
- package/benchmarks/production-v1/project/src/data/csv.js +3 -0
- package/benchmarks/production-v1/project/src/data/dedup.js +5 -0
- package/benchmarks/production-v1/project/src/data/migration.js +9 -0
- package/benchmarks/production-v1/project/src/frontend/pagination.js +8 -0
- package/benchmarks/production-v1/project/src/frontend/search-state.js +8 -0
- package/benchmarks/production-v1/project/src/frontend/unicode-search.js +7 -0
- package/benchmarks/production-v1/project/src/platform/args.js +10 -0
- package/benchmarks/production-v1/project/src/platform/config.js +7 -0
- package/benchmarks/production-v1/project/src/platform/workspace.js +13 -0
- package/benchmarks/production-v1/project/src/reliability/expiry.js +5 -0
- package/benchmarks/production-v1/project/src/reliability/retry.js +15 -0
- package/benchmarks/production-v1/project/test/smoke.test.js +12 -0
- package/benchmarks/production-v1/project/vendor/README.md +1 -0
- package/benchmarks/production-v1/prompts/bounded-retry.md +7 -0
- package/benchmarks/production-v1/prompts/cli-double-dash.md +7 -0
- package/benchmarks/production-v1/prompts/config-precedence.md +6 -0
- package/benchmarks/production-v1/prompts/destructive-history-refusal.md +3 -0
- package/benchmarks/production-v1/prompts/expiry-boundary.md +7 -0
- package/benchmarks/production-v1/prompts/incident-diagnosis.md +4 -0
- package/benchmarks/production-v1/prompts/invoice-rounding.md +9 -0
- package/benchmarks/production-v1/prompts/pagination-boundary.md +7 -0
- package/benchmarks/production-v1/prompts/protected-env-refusal.md +2 -0
- package/benchmarks/production-v1/prompts/quoted-csv.md +6 -0
- package/benchmarks/production-v1/prompts/repository-prompt-injection.md +5 -0
- package/benchmarks/production-v1/prompts/schema-migration.md +6 -0
- package/benchmarks/production-v1/prompts/stable-dedup.md +6 -0
- package/benchmarks/production-v1/prompts/stale-search-response.md +6 -0
- package/benchmarks/production-v1/prompts/tenant-cache-isolation.md +6 -0
- package/benchmarks/production-v1/prompts/tenant-role-authorization.md +6 -0
- package/benchmarks/production-v1/prompts/unicode-search.md +6 -0
- package/benchmarks/production-v1/prompts/workspace-order.md +7 -0
- package/benchmarks/production-v1/suite.json +284 -0
- package/benchmarks/production-v1/variant.mjs +112 -0
- package/docs/command-reference-vietnamese.md +15 -6
- package/docs/distribution-standard.md +7 -7
- package/docs/operator-manual-vietnamese.md +29 -13
- package/docs/quality-benchmark.md +129 -18
- package/docs/release-install-policy.md +15 -11
- package/docs/runtime-policy-design.md +2 -1
- package/docs/runtime-quality-baseline.md +13 -2
- package/docs/security-threat-model.md +1 -1
- package/docs/team-onboarding.md +2 -2
- package/docs-site/architecture.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/content/runtime.html +7 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +8 -2
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/package.json +1 -1
- package/packages/piagent-core/benchmark/benchmark-core.js +325 -41
- package/packages/piagent-core/extensions/piagent-guard.ts +47 -12
- package/packages/piagent-core/package.json +1 -1
- package/scripts/benchmark-runner.mjs +328 -68
- package/scripts/verify-local.sh +4 -0
|
@@ -23,6 +23,7 @@ import { summarizeSession, walkJsonl } from "./pi-usage-history.mjs";
|
|
|
23
23
|
|
|
24
24
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
25
25
|
const coreSuitePath = path.join(packageRoot, "benchmarks", "core-v1", "suite.json");
|
|
26
|
+
const productionSuitePath = path.join(packageRoot, "benchmarks", "production-v1", "suite.json");
|
|
26
27
|
const packageManifest = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
27
28
|
const outputLimit = 4 * 1024 * 1024;
|
|
28
29
|
const activeChildren = new Set();
|
|
@@ -45,6 +46,12 @@ const controlledCodexFeatures = [
|
|
|
45
46
|
"tool_suggest",
|
|
46
47
|
"workspace_dependencies"
|
|
47
48
|
];
|
|
49
|
+
const coldStartRuntimeManagedPaths = [
|
|
50
|
+
".pi/project-context.md",
|
|
51
|
+
".pi/context-index.json",
|
|
52
|
+
".pi/context-v2.sqlite",
|
|
53
|
+
".pi/context-v2.sqlite-*"
|
|
54
|
+
];
|
|
48
55
|
let interruptedSignal;
|
|
49
56
|
|
|
50
57
|
const usage = `Usage:
|
|
@@ -57,12 +64,17 @@ With no options it uses the built-in core-v1 suite, the current Pi default
|
|
|
57
64
|
model/thinking setting, and the suite's repeat count.
|
|
58
65
|
|
|
59
66
|
Options:
|
|
60
|
-
--suite <
|
|
67
|
+
--suite <id|path> core-v1, production-v1, or suite.json path.
|
|
68
|
+
--production Alias for --suite production-v1.
|
|
61
69
|
--surfaces <a,b> raw-pi,piagent (default) or piagent,codex-cli.
|
|
62
70
|
--model <provider/model> Pin one model identity for both surfaces.
|
|
63
71
|
--thinking <level> off|minimal|low|medium|high|xhigh|max.
|
|
64
72
|
--codex-mode <mode> controlled isolated home (default) or native user configuration.
|
|
65
73
|
--repeats <1-10> Override the suite repeat count.
|
|
74
|
+
--infrastructure-retries <n> Retry 0-3 startup failures with zero recorded usage.
|
|
75
|
+
--retry-delay <seconds> Backoff before infrastructure retry, 0-120 seconds.
|
|
76
|
+
--scenarios <id,id> Run selected scenario families for diagnosis.
|
|
77
|
+
--seed <value> Reproduce generated hidden variants.
|
|
66
78
|
--timeout <seconds> Per-agent timeout, 30-3600 seconds.
|
|
67
79
|
--output <directory> Report directory; must be empty or absent.
|
|
68
80
|
--keep-workspaces Retain isolated workspaces and session logs.
|
|
@@ -130,7 +142,11 @@ export function parseBenchmarkArgs(argv) {
|
|
|
130
142
|
model: undefined,
|
|
131
143
|
thinking: undefined,
|
|
132
144
|
codexMode: "controlled",
|
|
145
|
+
seed: undefined,
|
|
133
146
|
repeats: undefined,
|
|
147
|
+
infrastructureRetries: undefined,
|
|
148
|
+
retryDelaySeconds: undefined,
|
|
149
|
+
scenarioIds: undefined,
|
|
134
150
|
timeoutSeconds: undefined,
|
|
135
151
|
output: undefined,
|
|
136
152
|
keepWorkspaces: false,
|
|
@@ -149,6 +165,9 @@ export function parseBenchmarkArgs(argv) {
|
|
|
149
165
|
options.suite = requireValue(argv, index, arg);
|
|
150
166
|
index += 1;
|
|
151
167
|
break;
|
|
168
|
+
case "--production":
|
|
169
|
+
options.suite = "production-v1";
|
|
170
|
+
break;
|
|
152
171
|
case "--surfaces": {
|
|
153
172
|
const values = requireValue(argv, index, arg).split(",").map((value) => value.trim()).filter(Boolean);
|
|
154
173
|
if (values.length !== 2 || new Set(values).size !== 2 || values.some((value) => !benchmarkSurfaces.has(value))) {
|
|
@@ -179,6 +198,26 @@ export function parseBenchmarkArgs(argv) {
|
|
|
179
198
|
options.repeats = positiveInteger(requireValue(argv, index, arg), arg, 1, 10);
|
|
180
199
|
index += 1;
|
|
181
200
|
break;
|
|
201
|
+
case "--infrastructure-retries":
|
|
202
|
+
options.infrastructureRetries = positiveInteger(requireValue(argv, index, arg), arg, 0, 3);
|
|
203
|
+
index += 1;
|
|
204
|
+
break;
|
|
205
|
+
case "--retry-delay":
|
|
206
|
+
options.retryDelaySeconds = positiveInteger(requireValue(argv, index, arg), arg, 0, 120);
|
|
207
|
+
index += 1;
|
|
208
|
+
break;
|
|
209
|
+
case "--scenarios": {
|
|
210
|
+
const values = requireValue(argv, index, arg).split(",").map((value) => value.trim()).filter(Boolean);
|
|
211
|
+
if (values.length === 0 || new Set(values).size !== values.length) fail("--scenarios must contain unique scenario ids");
|
|
212
|
+
options.scenarioIds = values;
|
|
213
|
+
index += 1;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case "--seed":
|
|
217
|
+
options.seed = requireValue(argv, index, arg);
|
|
218
|
+
if (options.seed.length > 200) fail("--seed must contain at most 200 characters");
|
|
219
|
+
index += 1;
|
|
220
|
+
break;
|
|
182
221
|
case "--timeout":
|
|
183
222
|
options.timeoutSeconds = positiveInteger(requireValue(argv, index, arg), arg, 30, 3600);
|
|
184
223
|
index += 1;
|
|
@@ -232,7 +271,7 @@ function runCommand(command, args, options = {}) {
|
|
|
232
271
|
const cleanup = () => activeChildren.delete(child);
|
|
233
272
|
if (options.inherit) {
|
|
234
273
|
child.once("error", (error) => { cleanup(); reject(error); });
|
|
235
|
-
child.once("exit", (code, signal) => { cleanup(); resolve({ code: code ?? 1, signal, timedOut: false, stdout: "", stderr: "", forbiddenHits: [], durationSeconds: (Date.now() - started) / 1000 }); });
|
|
274
|
+
child.once("exit", (code, signal) => { cleanup(); resolve({ code: code ?? 1, signal, timedOut: false, stdout: "", stderr: "", forbiddenHits: [], requiredHits: [], durationSeconds: (Date.now() - started) / 1000 }); });
|
|
236
275
|
return;
|
|
237
276
|
}
|
|
238
277
|
if (options.input !== undefined) {
|
|
@@ -243,15 +282,22 @@ function runCommand(command, args, options = {}) {
|
|
|
243
282
|
let stderr = "";
|
|
244
283
|
const stdoutDigest = crypto.createHash("sha256");
|
|
245
284
|
const forbidden = [...new Set(options.forbiddenSubstrings ?? [])].filter((value) => typeof value === "string" && value);
|
|
285
|
+
const required = [...new Set(options.requiredSubstrings ?? [])].filter((value) => typeof value === "string" && value);
|
|
246
286
|
const forbiddenHits = new Set();
|
|
287
|
+
const requiredHits = new Set();
|
|
247
288
|
const scanTails = { stdout: "", stderr: "" };
|
|
248
|
-
const scanWindow = Math.max(0, ...forbidden.map((value) => value.length - 1));
|
|
289
|
+
const scanWindow = Math.max(0, ...[...forbidden, ...required].map((value) => value.length - 1));
|
|
249
290
|
const append = (current, chunk) => `${current}${chunk}`.slice(-outputLimit);
|
|
250
291
|
const inspect = (stream, chunk) => {
|
|
251
292
|
const text = `${scanTails[stream]}${chunk}`;
|
|
252
293
|
for (const value of forbidden) {
|
|
253
294
|
if (text.includes(value)) forbiddenHits.add(value);
|
|
254
295
|
}
|
|
296
|
+
if (stream === "stdout") {
|
|
297
|
+
for (const value of required) {
|
|
298
|
+
if (text.includes(value)) requiredHits.add(value);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
255
301
|
scanTails[stream] = scanWindow > 0 ? text.slice(-scanWindow) : "";
|
|
256
302
|
};
|
|
257
303
|
child.stdout.on("data", (chunk) => {
|
|
@@ -279,13 +325,14 @@ function runCommand(command, args, options = {}) {
|
|
|
279
325
|
child.once("exit", (code, signal) => {
|
|
280
326
|
if (timer) clearTimeout(timer);
|
|
281
327
|
cleanup();
|
|
282
|
-
resolve({ code: code ?? 1, signal, timedOut, stdout, stdoutHash: stdoutDigest.digest("hex"), stderr, forbiddenHits: [...forbiddenHits], durationSeconds: (Date.now() - started) / 1000 });
|
|
328
|
+
resolve({ code: code ?? 1, signal, timedOut, stdout, stdoutHash: stdoutDigest.digest("hex"), stderr, forbiddenHits: [...forbiddenHits], requiredHits: [...requiredHits], durationSeconds: (Date.now() - started) / 1000 });
|
|
283
329
|
});
|
|
284
330
|
});
|
|
285
331
|
}
|
|
286
332
|
|
|
287
333
|
function resolveSuitePath(input) {
|
|
288
334
|
if (input === "core-v1") return coreSuitePath;
|
|
335
|
+
if (input === "production-v1") return productionSuitePath;
|
|
289
336
|
const candidate = path.resolve(input);
|
|
290
337
|
return fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()
|
|
291
338
|
? path.join(candidate, "suite.json")
|
|
@@ -343,9 +390,15 @@ function validateSuiteFiles(suite, suiteRoot) {
|
|
|
343
390
|
const fixture = resolveSuiteEntry(suiteRoot, scenario.fixture, "fixture");
|
|
344
391
|
const prompt = resolveSuiteEntry(suiteRoot, scenario.prompt, "prompt");
|
|
345
392
|
const grader = resolveSuiteEntry(suiteRoot, scenario.grader, "grader");
|
|
393
|
+
const generator = scenario.variantGenerator
|
|
394
|
+
? resolveSuiteEntry(suiteRoot, scenario.variantGenerator, "variant generator")
|
|
395
|
+
: null;
|
|
346
396
|
if (inside(fixture, grader) || inside(fixture, prompt)) {
|
|
347
397
|
fail(`Suite prompt and grader must stay outside the agent fixture: ${scenario.id}`, 1);
|
|
348
398
|
}
|
|
399
|
+
if (generator && inside(fixture, generator)) {
|
|
400
|
+
fail(`Suite variant generator must stay outside the agent fixture: ${scenario.id}`, 1);
|
|
401
|
+
}
|
|
349
402
|
}
|
|
350
403
|
}
|
|
351
404
|
|
|
@@ -540,6 +593,46 @@ function applySetupFiles(workspace, setupFiles) {
|
|
|
540
593
|
}
|
|
541
594
|
}
|
|
542
595
|
|
|
596
|
+
function variantSeed(rootSeed, suiteDigest, scenarioId, repeat) {
|
|
597
|
+
return crypto.createHmac("sha256", rootSeed)
|
|
598
|
+
.update(`${suiteDigest}\0${scenarioId}\0${repeat}`)
|
|
599
|
+
.digest("hex");
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function generatedStringArray(value, field) {
|
|
603
|
+
if (value === undefined) return [];
|
|
604
|
+
if (!Array.isArray(value) || value.length > 20 || value.some((item) => typeof item !== "string" || !item || item.length > 1_000)) {
|
|
605
|
+
fail(`Benchmark variant ${field} must contain at most 20 non-empty strings of at most 1000 characters`, 1);
|
|
606
|
+
}
|
|
607
|
+
return [...new Set(value)];
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
async function generateVariant({ generator, workspace, oraclePath, seed, scenario, timeoutSeconds }) {
|
|
611
|
+
const result = await runCommand(process.execPath, [generator, workspace, oraclePath, seed, scenario.id], {
|
|
612
|
+
cwd: path.dirname(generator),
|
|
613
|
+
timeoutMs: Math.min(timeoutSeconds, 120) * 1_000,
|
|
614
|
+
env: graderEnvironment(scenario.id)
|
|
615
|
+
});
|
|
616
|
+
if (result.timedOut) fail(`Benchmark variant generator timed out for ${scenario.id}`, 1);
|
|
617
|
+
if (result.code !== 0) fail(`Benchmark variant generator failed for ${scenario.id}: ${result.stderr.trim() || result.stdout.trim()}`, 1);
|
|
618
|
+
let oracle;
|
|
619
|
+
try { oracle = JSON.parse(fs.readFileSync(oraclePath, "utf8")); }
|
|
620
|
+
catch (error) { fail(`Benchmark variant generator did not write a valid oracle for ${scenario.id}: ${error.message}`, 1); }
|
|
621
|
+
if (!oracle || typeof oracle !== "object" || Array.isArray(oracle) || oracle.schemaVersion !== 1 || !oracle.graderData || typeof oracle.graderData !== "object" || Array.isArray(oracle.graderData)) {
|
|
622
|
+
fail(`Benchmark variant oracle is invalid for ${scenario.id}`, 1);
|
|
623
|
+
}
|
|
624
|
+
const serialized = JSON.stringify(oracle);
|
|
625
|
+
if (Buffer.byteLength(serialized) > 100_000) fail(`Benchmark variant oracle is too large for ${scenario.id}`, 1);
|
|
626
|
+
writePrivate(oraclePath, `${JSON.stringify(oracle)}\n`);
|
|
627
|
+
return {
|
|
628
|
+
oraclePath,
|
|
629
|
+
oracleDigest: crypto.createHash("sha256").update(serialized).digest("hex"),
|
|
630
|
+
seedDigest: crypto.createHash("sha256").update(seed).digest("hex"),
|
|
631
|
+
requiredOutputSubstrings: generatedStringArray(oracle.requiredOutputSubstrings, "requiredOutputSubstrings"),
|
|
632
|
+
forbiddenOutputSubstrings: generatedStringArray(oracle.forbiddenOutputSubstrings, "forbiddenOutputSubstrings")
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
543
636
|
async function initializeTreatment(workspace, profile) {
|
|
544
637
|
const result = await runCommand("bash", [
|
|
545
638
|
path.join(packageRoot, "scripts", "init-project.sh"),
|
|
@@ -724,8 +817,9 @@ function parseGraderResult(stdout) {
|
|
|
724
817
|
};
|
|
725
818
|
}
|
|
726
819
|
|
|
727
|
-
async function gradeWorkspace(grader, workspace, scenario, timeoutSeconds) {
|
|
728
|
-
const
|
|
820
|
+
async function gradeWorkspace(grader, workspace, scenario, timeoutSeconds, oraclePath) {
|
|
821
|
+
const args = oraclePath ? [grader, workspace, oraclePath] : [grader, workspace];
|
|
822
|
+
const result = await runCommand(process.execPath, args, {
|
|
729
823
|
cwd: path.dirname(grader),
|
|
730
824
|
timeoutMs: Math.min(timeoutSeconds, 120) * 1_000,
|
|
731
825
|
env: graderEnvironment(scenario.id)
|
|
@@ -739,7 +833,7 @@ function sessionSummaries(sessionDir) {
|
|
|
739
833
|
return walkJsonl(sessionDir).map((file) => summarizeSession(file, {})).filter(Boolean);
|
|
740
834
|
}
|
|
741
835
|
|
|
742
|
-
function failureReason({ agent, grade, graderIntegrity, outsideScope, forbiddenHits }) {
|
|
836
|
+
function failureReason({ agent, grade, graderIntegrity, outsideScope, forbiddenHits, missingRequired }) {
|
|
743
837
|
const failures = [];
|
|
744
838
|
if (agent.timedOut) failures.push("agent-timeout");
|
|
745
839
|
else if (agent.code !== 0) failures.push(`agent-exit-${agent.code}`);
|
|
@@ -747,34 +841,81 @@ function failureReason({ agent, grade, graderIntegrity, outsideScope, forbiddenH
|
|
|
747
841
|
if (!graderIntegrity.passed) failures.push("grader-mutated-workspace");
|
|
748
842
|
if (outsideScope.length) failures.push(`outside-scope:${outsideScope.join(",")}`);
|
|
749
843
|
if (forbiddenHits.length) failures.push("forbidden-output");
|
|
844
|
+
if (missingRequired.length) failures.push("required-output-missing");
|
|
750
845
|
return failures.join("; ") || undefined;
|
|
751
846
|
}
|
|
752
847
|
|
|
848
|
+
function safeInfrastructureDiagnostic(value, forbiddenValues) {
|
|
849
|
+
let diagnostic = String(value ?? "");
|
|
850
|
+
for (const forbidden of forbiddenValues) {
|
|
851
|
+
if (forbidden) diagnostic = diagnostic.replaceAll(forbidden, "[REDACTED]");
|
|
852
|
+
}
|
|
853
|
+
return diagnostic
|
|
854
|
+
.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
855
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, " ")
|
|
856
|
+
.trim()
|
|
857
|
+
.slice(-4_000);
|
|
858
|
+
}
|
|
859
|
+
|
|
753
860
|
async function runOne({
|
|
754
861
|
suite,
|
|
755
862
|
suiteRoot,
|
|
756
863
|
scenario,
|
|
757
864
|
surface,
|
|
758
865
|
repeat,
|
|
866
|
+
infrastructureAttempt = 1,
|
|
759
867
|
runId,
|
|
760
868
|
runRoot,
|
|
761
869
|
options,
|
|
762
870
|
piCommand,
|
|
763
871
|
codexCommand,
|
|
764
872
|
codexDisabledFeatures,
|
|
765
|
-
codexRuntime
|
|
873
|
+
codexRuntime,
|
|
874
|
+
suiteDigest,
|
|
875
|
+
rootSeed
|
|
766
876
|
}) {
|
|
767
|
-
const
|
|
877
|
+
const attemptSuffix = infrastructureAttempt > 1 ? `-infra-${infrastructureAttempt}` : "";
|
|
878
|
+
const key = `${String(repeat).padStart(2, "0")}-${scenario.id}-${surface}${attemptSuffix}`;
|
|
768
879
|
const workspaceRoot = privateDirectory(path.join(runRoot, "workspaces", key));
|
|
769
880
|
const workspace = path.join(workspaceRoot, "project");
|
|
770
881
|
const sessions = privateDirectory(path.join(workspaceRoot, "sessions"));
|
|
771
882
|
const fixture = resolveSuiteEntry(suiteRoot, scenario.fixture, "fixture");
|
|
772
883
|
fs.cpSync(fixture, workspace, { recursive: true, errorOnExist: true });
|
|
773
884
|
applySetupFiles(workspace, scenario.setupFiles);
|
|
885
|
+
const profile = scenario.profile ?? suite.profile;
|
|
886
|
+
const lifecycle = scenario.lifecycle ?? "steady-state";
|
|
887
|
+
let variant = {
|
|
888
|
+
oraclePath: null,
|
|
889
|
+
oracleDigest: null,
|
|
890
|
+
seedDigest: null,
|
|
891
|
+
requiredOutputSubstrings: [],
|
|
892
|
+
forbiddenOutputSubstrings: []
|
|
893
|
+
};
|
|
894
|
+
if (scenario.variantGenerator) {
|
|
895
|
+
const generator = resolveSuiteEntry(suiteRoot, scenario.variantGenerator, "variant generator");
|
|
896
|
+
variant = await generateVariant({
|
|
897
|
+
generator,
|
|
898
|
+
workspace,
|
|
899
|
+
oraclePath: path.join(workspaceRoot, "oracle.json"),
|
|
900
|
+
seed: variantSeed(rootSeed, suiteDigest, scenario.id, repeat),
|
|
901
|
+
scenario,
|
|
902
|
+
timeoutSeconds: options.timeoutSeconds
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
const forbiddenOutputSubstrings = [...new Set([
|
|
906
|
+
...(scenario.forbiddenOutputSubstrings ?? []),
|
|
907
|
+
...variant.forbiddenOutputSubstrings
|
|
908
|
+
])];
|
|
909
|
+
const requiredOutputSubstrings = [...new Set([
|
|
910
|
+
...(scenario.requiredOutputSubstrings ?? []),
|
|
911
|
+
...variant.requiredOutputSubstrings
|
|
912
|
+
])];
|
|
774
913
|
if (surface === "piagent") {
|
|
775
|
-
await initializeTreatment(workspace,
|
|
776
|
-
|
|
777
|
-
|
|
914
|
+
await initializeTreatment(workspace, profile);
|
|
915
|
+
if (lifecycle === "steady-state") {
|
|
916
|
+
prepareTreatmentBaseline(workspace, profile, scenario);
|
|
917
|
+
await prepareTreatmentContextEngine(workspace);
|
|
918
|
+
}
|
|
778
919
|
}
|
|
779
920
|
await initializeGit(workspace, scenario.setupFiles);
|
|
780
921
|
|
|
@@ -817,14 +958,15 @@ async function runOne({
|
|
|
817
958
|
const codexCollector = surface === "codex-cli" ? createCodexExecJsonlCollector({
|
|
818
959
|
model: options.model,
|
|
819
960
|
thinkingLevel: options.thinking,
|
|
820
|
-
onEvent: (event) => inspectForbiddenValue(event,
|
|
961
|
+
onEvent: (event) => inspectForbiddenValue(event, forbiddenOutputSubstrings, codexForbiddenHits)
|
|
821
962
|
}) : undefined;
|
|
822
963
|
|
|
823
964
|
const agent = await runCommand(command, args, {
|
|
824
965
|
cwd: workspace,
|
|
825
966
|
input: surface === "codex-cli" ? prompt : undefined,
|
|
826
967
|
timeoutMs: options.timeoutSeconds * 1_000,
|
|
827
|
-
forbiddenSubstrings:
|
|
968
|
+
forbiddenSubstrings: forbiddenOutputSubstrings,
|
|
969
|
+
requiredSubstrings: requiredOutputSubstrings,
|
|
828
970
|
onStdoutChunk: codexCollector ? (chunk) => codexCollector.write(chunk) : undefined,
|
|
829
971
|
env: surface === "codex-cli" ? codexProcessEnvironment(codexRuntime, {
|
|
830
972
|
PIAGENT_NO_UPDATE_CHECK: "1",
|
|
@@ -832,6 +974,8 @@ async function runOne({
|
|
|
832
974
|
PIAGENT_BENCHMARK_SCENARIO: scenario.id,
|
|
833
975
|
PIAGENT_BENCHMARK_SURFACE: surface,
|
|
834
976
|
PIAGENT_BENCHMARK_SESSION_ID: sessionId,
|
|
977
|
+
PIAGENT_BENCHMARK_PROFILE: profile,
|
|
978
|
+
PIAGENT_BENCHMARK_LIFECYCLE: lifecycle,
|
|
835
979
|
NO_COLOR: "1"
|
|
836
980
|
}) : benchmarkEnvironment({
|
|
837
981
|
PIAGENT_NO_UPDATE_CHECK: "1",
|
|
@@ -839,11 +983,14 @@ async function runOne({
|
|
|
839
983
|
PIAGENT_BENCHMARK_SCENARIO: scenario.id,
|
|
840
984
|
PIAGENT_BENCHMARK_SURFACE: surface,
|
|
841
985
|
PIAGENT_BENCHMARK_SESSION_ID: sessionId,
|
|
986
|
+
PIAGENT_BENCHMARK_PROFILE: profile,
|
|
987
|
+
PIAGENT_BENCHMARK_LIFECYCLE: lifecycle,
|
|
842
988
|
NO_COLOR: "1"
|
|
843
989
|
})
|
|
844
990
|
});
|
|
845
991
|
const sessionFiles = surface === "codex-cli" ? [] : walkJsonl(sessions);
|
|
846
992
|
let usage;
|
|
993
|
+
let codexDiagnostics = [];
|
|
847
994
|
if (surface === "codex-cli") {
|
|
848
995
|
try {
|
|
849
996
|
usage = codexCollector.finish();
|
|
@@ -851,6 +998,7 @@ async function runOne({
|
|
|
851
998
|
if (agent.code === 0 && !agent.timedOut) throw error;
|
|
852
999
|
usage = aggregateSessionUsage([]);
|
|
853
1000
|
}
|
|
1001
|
+
codexDiagnostics = codexCollector.diagnostics();
|
|
854
1002
|
} else {
|
|
855
1003
|
usage = aggregateSessionUsage(sessionSummaries(sessions));
|
|
856
1004
|
}
|
|
@@ -858,54 +1006,104 @@ async function runOne({
|
|
|
858
1006
|
...(agent.forbiddenHits ?? []),
|
|
859
1007
|
...(surface === "codex-cli"
|
|
860
1008
|
? [...codexForbiddenHits]
|
|
861
|
-
: forbiddenSessionHits(sessionFiles,
|
|
1009
|
+
: forbiddenSessionHits(sessionFiles, forbiddenOutputSubstrings))
|
|
862
1010
|
])];
|
|
863
|
-
const
|
|
1011
|
+
const requiredHits = new Set(agent.requiredHits ?? []);
|
|
1012
|
+
if (surface !== "codex-cli") {
|
|
1013
|
+
for (const value of forbiddenSessionHits(sessionFiles, requiredOutputSubstrings)) requiredHits.add(value);
|
|
1014
|
+
}
|
|
1015
|
+
const missingRequired = requiredOutputSubstrings.filter((value) => !requiredHits.has(value));
|
|
1016
|
+
const allChangedFiles = workingTreeFiles(workspace);
|
|
1017
|
+
const runtimeManagedChanges = surface === "piagent" && lifecycle === "cold-start"
|
|
1018
|
+
? allChangedFiles.filter((file) => matchesAnyPath(file, coldStartRuntimeManagedPaths))
|
|
1019
|
+
: [];
|
|
1020
|
+
const runtimeManagedSet = new Set(runtimeManagedChanges);
|
|
1021
|
+
const changedFiles = allChangedFiles.filter((file) => !runtimeManagedSet.has(file));
|
|
864
1022
|
const beforeGrade = workingTreeSnapshot(workspace);
|
|
865
1023
|
const outsideScope = changedFiles.filter((file) => !matchesAnyPath(file, scenario.allowedChanges));
|
|
866
|
-
const grade = await gradeWorkspace(graderPath, workspace, scenario, options.timeoutSeconds);
|
|
1024
|
+
const grade = await gradeWorkspace(graderPath, workspace, scenario, options.timeoutSeconds, variant.oraclePath);
|
|
867
1025
|
const afterGrade = workingTreeSnapshot(workspace);
|
|
868
1026
|
const graderIntegrity = { passed: JSON.stringify(beforeGrade) === JSON.stringify(afterGrade) };
|
|
869
1027
|
let workflow = null;
|
|
870
|
-
if (surface === "piagent" && scenario.kind
|
|
1028
|
+
if (surface === "piagent" && scenario.kind !== "safety-refusal") {
|
|
871
1029
|
const task = listTaskContracts(workspace).find((item) => item.sessionId === sessionId);
|
|
872
|
-
workflow = evaluateWorkflowEvidence(task, changedFiles, usage.toolNames);
|
|
1030
|
+
workflow = evaluateWorkflowEvidence(task, changedFiles, usage.toolNames, { scenarioKind: scenario.kind });
|
|
873
1031
|
}
|
|
874
|
-
const scope = { passed: outsideScope.length === 0, changedFiles, outsideScope };
|
|
1032
|
+
const scope = { passed: outsideScope.length === 0, changedFiles, outsideScope, allChangedFiles, runtimeManagedChanges };
|
|
875
1033
|
const outputSafety = { passed: forbiddenHits.length === 0, forbiddenHits: forbiddenHits.map((value) => crypto.createHash("sha256").update(value).digest("hex")) };
|
|
876
|
-
const
|
|
877
|
-
|
|
1034
|
+
const outputEvidence = {
|
|
1035
|
+
passed: missingRequired.length === 0,
|
|
1036
|
+
requiredCount: requiredOutputSubstrings.length,
|
|
1037
|
+
observedCount: requiredOutputSubstrings.length - missingRequired.length,
|
|
1038
|
+
missingHashes: missingRequired.map((value) => crypto.createHash("sha256").update(value).digest("hex"))
|
|
1039
|
+
};
|
|
1040
|
+
// A timed-out agent is measured reliability evidence. Only an early process
|
|
1041
|
+
// failure with no provider usage is infrastructure and eligible for retry.
|
|
1042
|
+
const abortSuite = !agent.timedOut && agent.code !== 0 && usage.fresh <= 0;
|
|
1043
|
+
const diagnosticInput = codexDiagnostics.length > 0
|
|
1044
|
+
? JSON.stringify(codexDiagnostics)
|
|
1045
|
+
: `${agent.stderr ?? ""}\n${agent.stdout ?? ""}`;
|
|
1046
|
+
const resolved = agent.code === 0 && !agent.timedOut && grade.passed && graderIntegrity.passed && scope.passed && outputSafety.passed && outputEvidence.passed;
|
|
878
1047
|
const record = {
|
|
879
1048
|
schemaVersion: 1,
|
|
880
1049
|
scenarioId: scenario.id,
|
|
881
1050
|
scenarioTitle: scenario.title,
|
|
882
1051
|
scenarioKind: scenario.kind,
|
|
1052
|
+
category: scenario.category ?? "unspecified",
|
|
1053
|
+
difficulty: scenario.difficulty ?? "unspecified",
|
|
1054
|
+
profile,
|
|
1055
|
+
lifecycle,
|
|
883
1056
|
surface,
|
|
884
1057
|
repeat,
|
|
1058
|
+
infrastructureAttempt,
|
|
885
1059
|
sessionId,
|
|
886
1060
|
providerSessionId: usage.providerSessionId ?? null,
|
|
887
1061
|
abortSuite,
|
|
1062
|
+
infrastructureFailure: abortSuite ? `agent-exit-${agent.code}-before-usage` : undefined,
|
|
1063
|
+
infrastructureDiagnostic: abortSuite ? safeInfrastructureDiagnostic(diagnosticInput, forbiddenOutputSubstrings) : undefined,
|
|
1064
|
+
infrastructureDiagnosticSource: abortSuite ? (codexDiagnostics.length > 0 ? "codex-error-events" : "process-output-tail") : undefined,
|
|
888
1065
|
resolved,
|
|
889
|
-
failure: failureReason({ agent, grade, graderIntegrity, outsideScope, forbiddenHits }),
|
|
890
|
-
agent: {
|
|
1066
|
+
failure: failureReason({ agent, grade, graderIntegrity, outsideScope, forbiddenHits, missingRequired }),
|
|
1067
|
+
agent: {
|
|
1068
|
+
exitCode: agent.code,
|
|
1069
|
+
signal: agent.signal,
|
|
1070
|
+
timedOut: agent.timedOut,
|
|
1071
|
+
stdoutHash: agent.stdoutHash ?? crypto.createHash("sha256").update(agent.stdout).digest("hex"),
|
|
1072
|
+
stderrHash: crypto.createHash("sha256").update(agent.stderr ?? "").digest("hex")
|
|
1073
|
+
},
|
|
891
1074
|
grade,
|
|
892
1075
|
graderIntegrity,
|
|
893
1076
|
scope,
|
|
894
1077
|
outputSafety,
|
|
1078
|
+
outputEvidence,
|
|
895
1079
|
workflow,
|
|
896
1080
|
usage,
|
|
897
1081
|
durationSeconds: agent.durationSeconds,
|
|
898
|
-
promptHash: crypto.createHash("sha256").update(prompt).digest("hex")
|
|
1082
|
+
promptHash: crypto.createHash("sha256").update(prompt).digest("hex"),
|
|
1083
|
+
variant: scenario.variantGenerator ? {
|
|
1084
|
+
generated: true,
|
|
1085
|
+
seedDigest: variant.seedDigest,
|
|
1086
|
+
oracleDigest: variant.oracleDigest
|
|
1087
|
+
} : { generated: false }
|
|
899
1088
|
};
|
|
900
1089
|
if (!options.keepWorkspaces) fs.rmSync(workspaceRoot, { recursive: true, force: true });
|
|
901
1090
|
return record;
|
|
902
1091
|
}
|
|
903
1092
|
|
|
904
|
-
function executionOrder(suite, repeats, surfaces) {
|
|
1093
|
+
function executionOrder(suite, repeats, surfaces, rootSeed) {
|
|
905
1094
|
const order = [];
|
|
906
1095
|
for (let repeat = 1; repeat <= repeats; repeat += 1) {
|
|
907
|
-
|
|
908
|
-
|
|
1096
|
+
const scenarios = suite.schemaVersion === 2
|
|
1097
|
+
? [...suite.scenarios].sort((left, right) => {
|
|
1098
|
+
const rank = (scenario) => crypto.createHmac("sha256", rootSeed).update(`order\0${repeat}\0${scenario.id}`).digest("hex");
|
|
1099
|
+
return rank(left).localeCompare(rank(right));
|
|
1100
|
+
})
|
|
1101
|
+
: suite.scenarios;
|
|
1102
|
+
for (const [index, scenario] of scenarios.entries()) {
|
|
1103
|
+
const reverse = suite.schemaVersion === 2
|
|
1104
|
+
? (crypto.createHmac("sha256", rootSeed).update(`surface\0${repeat}\0${scenario.id}`).digest()[0] & 1) === 1
|
|
1105
|
+
: (repeat + index) % 2 !== 0;
|
|
1106
|
+
const ordered = reverse ? [...surfaces].reverse() : surfaces;
|
|
909
1107
|
for (const surface of ordered) order.push({ scenario, surface, repeat });
|
|
910
1108
|
}
|
|
911
1109
|
}
|
|
@@ -987,29 +1185,44 @@ async function main() {
|
|
|
987
1185
|
}
|
|
988
1186
|
const { suite, manifestPath, suiteRoot } = loadSuite(options.suite);
|
|
989
1187
|
validateSuiteFiles(suite, suiteRoot);
|
|
1188
|
+
const declaredScenarioCount = suite.scenarios.length;
|
|
1189
|
+
if (options.scenarioIds) {
|
|
1190
|
+
const byId = new Map(suite.scenarios.map((scenario) => [scenario.id, scenario]));
|
|
1191
|
+
const missing = options.scenarioIds.filter((id) => !byId.has(id));
|
|
1192
|
+
if (missing.length) fail(`Unknown benchmark scenario: ${missing.join(", ")}`, 1);
|
|
1193
|
+
suite.scenarios = options.scenarioIds.map((id) => byId.get(id));
|
|
1194
|
+
}
|
|
990
1195
|
options.repeats = options.repeats ?? suite.defaultRepeats;
|
|
1196
|
+
options.infrastructureRetries = options.infrastructureRetries ?? (suite.schemaVersion === 2 ? 2 : 0);
|
|
1197
|
+
options.retryDelaySeconds = options.retryDelaySeconds ?? (suite.schemaVersion === 2 ? 60 : 0);
|
|
991
1198
|
options.timeoutSeconds = options.timeoutSeconds ?? suite.timeoutSeconds;
|
|
992
1199
|
if (options.surfaces.includes("codex-cli")) {
|
|
993
1200
|
codexModelName(options.model);
|
|
994
1201
|
codexThinkingEffort(options.thinking);
|
|
995
1202
|
}
|
|
996
1203
|
const comparison = comparisonSurfaces(options);
|
|
997
|
-
const order = executionOrder(suite, options.repeats, options.surfaces);
|
|
998
1204
|
const piCommand = process.env.PIAGENT_BENCHMARK_PI_COMMAND || "pi";
|
|
999
1205
|
const codexCommand = process.env.PIAGENT_BENCHMARK_CODEX_COMMAND || "codex";
|
|
1000
1206
|
const suiteDigest = suiteTreeDigest(suiteRoot);
|
|
1207
|
+
const rootSeed = options.seed ?? crypto.randomBytes(32).toString("hex");
|
|
1208
|
+
const rootSeedDigest = crypto.createHash("sha256").update(rootSeed).digest("hex");
|
|
1209
|
+
const order = executionOrder(suite, options.repeats, options.surfaces, rootSeed);
|
|
1210
|
+
const lifecycles = [...new Set(suite.scenarios.map((scenario) => scenario.lifecycle ?? "steady-state"))];
|
|
1001
1211
|
const plan = [
|
|
1002
1212
|
"Piagent automatic benchmark",
|
|
1003
1213
|
` platform: v${packageManifest.version}`,
|
|
1004
|
-
` suite: ${suite.id} (${suite.scenarios.length} scenarios)`,
|
|
1214
|
+
` suite: ${suite.id} (${suite.scenarios.length}${suite.scenarios.length !== declaredScenarioCount ? `/${declaredScenarioCount}` : ""} scenarios)`,
|
|
1005
1215
|
` digest: ${suiteDigest.slice(0, 16)}`,
|
|
1006
1216
|
` surfaces: ${options.surfaces.join(", ")}`,
|
|
1007
1217
|
` compare: ${benchmarkSurfaceLabel(comparison.candidateSurface)} vs ${benchmarkSurfaceLabel(comparison.baselineSurface)}`,
|
|
1008
1218
|
` repeats: ${options.repeats}`,
|
|
1219
|
+
` retries: ${options.infrastructureRetries} infrastructure-only · ${options.retryDelaySeconds}s backoff`,
|
|
1009
1220
|
` sessions: ${order.length}`,
|
|
1010
1221
|
` model: ${options.model ?? "Pi default"}`,
|
|
1011
1222
|
` thinking: ${options.thinking ?? "Pi default"}`,
|
|
1012
|
-
|
|
1223
|
+
` lifecycle: ${lifecycles.join(", ")}`,
|
|
1224
|
+
` variants: ${suite.scenarios.some((scenario) => scenario.variantGenerator) ? `generated · seed ${rootSeedDigest.slice(0, 16)}` : "static"}`,
|
|
1225
|
+
` ordering: ${suite.schemaVersion === 2 ? "seeded paired blocks" : "paired alternating"}`,
|
|
1013
1226
|
` timeout: ${options.timeoutSeconds}s per session`,
|
|
1014
1227
|
" grading: hidden verifier + scope + output safety + Pi task evidence"
|
|
1015
1228
|
].join("\n");
|
|
@@ -1041,50 +1254,89 @@ async function main() {
|
|
|
1041
1254
|
const runs = [];
|
|
1042
1255
|
let fatalRunError;
|
|
1043
1256
|
const ledgerPath = path.join(runRoot, "runs.jsonl");
|
|
1257
|
+
const infrastructureLedgerPath = path.join(runRoot, "infrastructure-attempts.jsonl");
|
|
1044
1258
|
try {
|
|
1045
1259
|
for (const [index, item] of order.entries()) {
|
|
1046
1260
|
if (interruptedSignal) break;
|
|
1047
1261
|
process.stdout.write(`[${index + 1}/${order.length}] ${item.scenario.id} · ${item.surface} · repeat ${item.repeat}\n`);
|
|
1048
1262
|
let record;
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1263
|
+
const infrastructureFailures = [];
|
|
1264
|
+
for (let infrastructureAttempt = 1; infrastructureAttempt <= options.infrastructureRetries + 1; infrastructureAttempt += 1) {
|
|
1265
|
+
let attemptError;
|
|
1266
|
+
let attemptCodexRuntime = codexRuntime;
|
|
1267
|
+
try {
|
|
1268
|
+
if (item.surface === "codex-cli") attemptCodexRuntime = createCodexRuntime(options);
|
|
1269
|
+
record = await runOne({
|
|
1270
|
+
suite,
|
|
1271
|
+
suiteRoot,
|
|
1272
|
+
...item,
|
|
1273
|
+
infrastructureAttempt,
|
|
1274
|
+
runId,
|
|
1275
|
+
runRoot,
|
|
1276
|
+
options,
|
|
1277
|
+
piCommand,
|
|
1278
|
+
codexCommand,
|
|
1279
|
+
codexDisabledFeatures: runtime.codexDisabledFeatures,
|
|
1280
|
+
codexRuntime: attemptCodexRuntime,
|
|
1281
|
+
suiteDigest,
|
|
1282
|
+
rootSeed
|
|
1283
|
+
});
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
attemptError = error;
|
|
1286
|
+
record = {
|
|
1287
|
+
schemaVersion: 1,
|
|
1288
|
+
scenarioId: item.scenario.id,
|
|
1289
|
+
scenarioTitle: item.scenario.title,
|
|
1290
|
+
scenarioKind: item.scenario.kind,
|
|
1291
|
+
category: item.scenario.category ?? "unspecified",
|
|
1292
|
+
difficulty: item.scenario.difficulty ?? "unspecified",
|
|
1293
|
+
profile: item.scenario.profile ?? suite.profile,
|
|
1294
|
+
lifecycle: item.scenario.lifecycle ?? "steady-state",
|
|
1295
|
+
surface: item.surface,
|
|
1296
|
+
repeat: item.repeat,
|
|
1297
|
+
infrastructureAttempt,
|
|
1298
|
+
abortSuite: true,
|
|
1299
|
+
infrastructureFailure: `runner-error:${error.message}`,
|
|
1300
|
+
resolved: false,
|
|
1301
|
+
failure: `runner-error:${error.message}`,
|
|
1302
|
+
grade: { passed: false, score: 0, checks: [] },
|
|
1303
|
+
graderIntegrity: { passed: false },
|
|
1304
|
+
scope: { passed: false, changedFiles: [], outsideScope: [] },
|
|
1305
|
+
outputSafety: { passed: false, forbiddenHits: [] },
|
|
1306
|
+
outputEvidence: { passed: false, requiredCount: 0, observedCount: 0, missingHashes: [] },
|
|
1307
|
+
workflow: null,
|
|
1308
|
+
usage: aggregateSessionUsage([]),
|
|
1309
|
+
durationSeconds: 0
|
|
1310
|
+
};
|
|
1311
|
+
} finally {
|
|
1312
|
+
if (attemptCodexRuntime !== codexRuntime) attemptCodexRuntime.cleanup();
|
|
1313
|
+
}
|
|
1314
|
+
if (!record.abortSuite || interruptedSignal) break;
|
|
1315
|
+
const retryAvailable = infrastructureAttempt <= options.infrastructureRetries;
|
|
1316
|
+
infrastructureFailures.push({
|
|
1317
|
+
attempt: infrastructureAttempt,
|
|
1318
|
+
failure: record.infrastructureFailure ?? record.failure,
|
|
1319
|
+
agent: record.agent,
|
|
1320
|
+
durationSeconds: record.durationSeconds
|
|
1061
1321
|
});
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
abortSuite: true,
|
|
1072
|
-
resolved: false,
|
|
1073
|
-
failure: `runner-error:${error.message}`,
|
|
1074
|
-
grade: { passed: false, score: 0, checks: [] },
|
|
1075
|
-
graderIntegrity: { passed: false },
|
|
1076
|
-
scope: { passed: false, changedFiles: [], outsideScope: [] },
|
|
1077
|
-
outputSafety: { passed: false, forbiddenHits: [] },
|
|
1078
|
-
workflow: null,
|
|
1079
|
-
usage: aggregateSessionUsage([]),
|
|
1080
|
-
durationSeconds: 0
|
|
1081
|
-
};
|
|
1322
|
+
fs.appendFileSync(infrastructureLedgerPath, `${JSON.stringify({ ...record, accepted: false, retryAvailable })}\n`, { mode: 0o600 });
|
|
1323
|
+
if (!retryAvailable) {
|
|
1324
|
+
if (attemptError) fatalRunError = attemptError;
|
|
1325
|
+
break;
|
|
1326
|
+
}
|
|
1327
|
+
process.stdout.write(` RETRY ${infrastructureAttempt}/${options.infrastructureRetries} (${record.infrastructureFailure ?? record.failure})\n`);
|
|
1328
|
+
if (options.retryDelaySeconds > 0) {
|
|
1329
|
+
await new Promise((resolve) => setTimeout(resolve, options.retryDelaySeconds * 1_000));
|
|
1330
|
+
}
|
|
1082
1331
|
}
|
|
1332
|
+
record.infrastructureAttempts = record.infrastructureAttempt ?? 1;
|
|
1333
|
+
record.infrastructureRetries = Math.max(0, record.infrastructureAttempts - 1);
|
|
1334
|
+
record.infrastructureFailures = infrastructureFailures;
|
|
1083
1335
|
runs.push(record);
|
|
1084
1336
|
fs.appendFileSync(ledgerPath, `${JSON.stringify(record)}\n`, { mode: 0o600 });
|
|
1085
1337
|
const cost = Number.isFinite(record.usage.cost) ? `$${Number(record.usage.cost).toFixed(6)}` : "cost n/a";
|
|
1086
1338
|
process.stdout.write(` ${record.resolved ? "PASS" : `FAIL (${record.failure})`} · ${record.usage.fresh} fresh tok · ${cost}\n`);
|
|
1087
|
-
if (record.abortSuite && !fatalRunError) fatalRunError = new Error(record.failure ?? "agent startup failure");
|
|
1339
|
+
if (record.abortSuite && !fatalRunError) fatalRunError = new Error(record.infrastructureFailure ?? record.failure ?? "agent startup failure");
|
|
1088
1340
|
if (fatalRunError) break;
|
|
1089
1341
|
}
|
|
1090
1342
|
} finally {
|
|
@@ -1113,12 +1365,17 @@ async function main() {
|
|
|
1113
1365
|
environment: {
|
|
1114
1366
|
platformVersion: packageManifest.version,
|
|
1115
1367
|
suiteDigest,
|
|
1368
|
+
variantRootSeed: suite.scenarios.some((scenario) => scenario.variantGenerator) ? rootSeed : null,
|
|
1369
|
+
variantRootSeedDigest: suite.scenarios.some((scenario) => scenario.variantGenerator) ? rootSeedDigest : null,
|
|
1370
|
+
executionOrder: suite.schemaVersion === 2 ? "seeded-paired-block-randomized" : "paired-alternating",
|
|
1116
1371
|
profile: suite.profile,
|
|
1117
1372
|
requestedModel: options.model ?? null,
|
|
1118
1373
|
requestedThinking: options.thinking ?? null,
|
|
1119
|
-
treatmentBaseline:
|
|
1120
|
-
? "
|
|
1121
|
-
|
|
1374
|
+
treatmentBaseline: lifecycles.length === 1 && lifecycles[0] === "steady-state"
|
|
1375
|
+
? options.surfaces.includes("codex-cli")
|
|
1376
|
+
? "piagent-initialized-and-onboarded; codex-clean-fixture"
|
|
1377
|
+
: "initialized-and-onboarded"
|
|
1378
|
+
: "scenario-defined-mixed-lifecycle",
|
|
1122
1379
|
timeoutSeconds: options.timeoutSeconds,
|
|
1123
1380
|
nodeVersion: process.version,
|
|
1124
1381
|
piVersion: runtime.piVersion,
|
|
@@ -1126,7 +1383,7 @@ async function main() {
|
|
|
1126
1383
|
codexMode: options.surfaces.includes("codex-cli") ? options.codexMode : null,
|
|
1127
1384
|
codexAuth: runtime.codexAuth ?? null,
|
|
1128
1385
|
codexIsolation: options.surfaces.includes("codex-cli")
|
|
1129
|
-
? options.codexMode === "controlled" ? "temporary-home" : "operator-home"
|
|
1386
|
+
? options.codexMode === "controlled" ? "per-session-temporary-home" : "operator-home"
|
|
1130
1387
|
: null,
|
|
1131
1388
|
codexCredentialBridge: options.surfaces.includes("codex-cli") ? codexRuntime.credentialBridge : null,
|
|
1132
1389
|
codexGlobalInstructions: options.surfaces.includes("codex-cli")
|
|
@@ -1134,6 +1391,7 @@ async function main() {
|
|
|
1134
1391
|
: null,
|
|
1135
1392
|
codexDisabledFeatures: runtime.codexDisabledFeatures,
|
|
1136
1393
|
surfaces: options.surfaces,
|
|
1394
|
+
scenarioSelection: options.scenarioIds ?? null,
|
|
1137
1395
|
surfaceModels: options.surfaces.includes("codex-cli") ? {
|
|
1138
1396
|
piagent: options.model,
|
|
1139
1397
|
"codex-cli": codexModelName(options.model)
|
|
@@ -1158,6 +1416,8 @@ async function main() {
|
|
|
1158
1416
|
|| report.comparison.reliabilityGate === false
|
|
1159
1417
|
|| report.comparison.qualityNonInferior === false
|
|
1160
1418
|
|| report.comparison.workflowGate === false
|
|
1419
|
+
|| report.comparison.categoryGate === false
|
|
1420
|
+
|| report.comparison.productionGate?.passed === false
|
|
1161
1421
|
) process.exitCode = 1;
|
|
1162
1422
|
} finally {
|
|
1163
1423
|
codexRuntime.cleanup();
|