@lesliechan721/agent-skill-eval 0.1.0-beta.38 → 0.1.0-beta.39

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.
@@ -5,11 +5,13 @@ import fs from 'fs-extra';
5
5
  import { CodexEvalAttemptControl, EvalAppServerSession, extractAppServerThreadId, assertHostReadContainmentAvailable, hostReadContainmentProfile, } from './codex-eval-app-server.js';
6
6
  import { applyCodexEvalProviderEnvironment, createCodexEvalTemporaryRoot, materializeCodexEvalRuntimeHome, redactCodexEvalValue, } from './codex-eval-runtime-config.js';
7
7
  import { applyCodexEvalGitIsolation } from './codex-eval-isolation.js';
8
+ import { parseInstructionV2ReasoningEffort, parseInstructionV2Receipt, parseInstructionV2Transport, } from './instruction-v2.js';
8
9
  import { runProviderProcess } from './process.js';
9
10
  import { assertProviderRunPaths } from './run-paths.js';
10
11
  import { parseEvalProviderGrade, } from './custom-process.js';
11
12
  import { createCodexGrader, parseConversationGraderOutput } from './providers.js';
12
13
  import { parseWorkflowEvaluation, normalizeWorkflowChecks, WorkflowEvaluation, } from './codex-workflow-evaluation.js';
14
+ import { runWorkflowFixtureAction, validateWorkflowFixtureActions } from './codex-workflow-fixtures.js';
13
15
  const MAX_ADAPTER_OUTPUT_BYTES = 4 * 1024 * 1024;
14
16
  const MAX_REQUEST_TEXT_BYTES = 200_000;
15
17
  const MAX_ROLE_COUNT = 32;
@@ -70,10 +72,10 @@ async function assertWorkspaceRoleFile(workspace, relative) {
70
72
  throw new Error(`workflow role configFile must be a regular file: ${relative}`);
71
73
  }
72
74
  }
73
- export async function parseCodexWorkflowRequest(value, allowedModels, workspace) {
75
+ export async function parseCodexWorkflowRequest(value, allowedModels, workspace, fixtures) {
74
76
  if (!isRecord(value))
75
77
  throw new Error('workflow prepare stdout must be one JSON object.');
76
- exactKeys(value, ['version', 'prompt', 'sandbox', 'model', 'developerInstructions', 'roles', 'multiAgent'], ['evaluation'], 'workflow prepare stdout');
78
+ exactKeys(value, ['version', 'prompt', 'sandbox', 'model', 'developerInstructions', 'roles', 'multiAgent'], ['evaluation', 'transport', 'reasoningEffort'], 'workflow prepare stdout');
77
79
  if (value.version !== 1)
78
80
  throw new Error('workflow prepare stdout version must be 1.');
79
81
  if (value.sandbox !== 'read-only' && value.sandbox !== 'workspace-write') {
@@ -121,14 +123,16 @@ export async function parseCodexWorkflowRequest(value, allowedModels, workspace)
121
123
  if (multiAgent.protocol === 'v1'
122
124
  && (multiAgent.hideSpawnAgentMetadata || multiAgent.exposeSpawnAgentModelOverrides))
123
125
  throw new Error('workflow request V1 metadata fields must be false.');
124
- return {
126
+ const request = {
125
127
  version: 1,
126
128
  prompt: boundedString(value.prompt, 'workflow request prompt', MAX_REQUEST_TEXT_BYTES),
127
129
  sandbox: value.sandbox,
128
130
  model: value.model,
131
+ ...(value.reasoningEffort === undefined ? {} : { reasoningEffort: parseInstructionV2ReasoningEffort(value.reasoningEffort) }),
129
132
  developerInstructions: boundedString(value.developerInstructions, 'workflow request developerInstructions', MAX_REQUEST_TEXT_BYTES),
130
133
  roles,
131
134
  ...(value.evaluation === undefined ? {} : { evaluation: parseWorkflowEvaluation(value.evaluation) }),
135
+ ...(value.transport === undefined ? {} : { transport: await parseInstructionV2Transport(value.transport, workspace) }),
132
136
  multiAgent: {
133
137
  protocol: multiAgent.protocol,
134
138
  maxDepth: multiAgent.maxDepth,
@@ -136,6 +140,28 @@ export async function parseCodexWorkflowRequest(value, allowedModels, workspace)
136
140
  exposeSpawnAgentModelOverrides: multiAgent.exposeSpawnAgentModelOverrides,
137
141
  },
138
142
  };
143
+ if (request.transport) {
144
+ const external = request.transport.request;
145
+ if (external.instructions.text !== request.developerInstructions || external.task !== request.prompt
146
+ || external.run.access !== request.sandbox || (external.run.model ?? null) !== request.model
147
+ || external.run.reasoningEffort !== request.reasoningEffort) {
148
+ throw new Error('instruction-v2 request must match workflow instructions, task, access, model, and reasoningEffort.');
149
+ }
150
+ if (request.evaluation?.interactions.length || request.evaluation?.setupPrompt !== undefined) {
151
+ throw new Error('instruction-v2 transport does not support evaluation interactions or setupPrompt.');
152
+ }
153
+ }
154
+ const actions = [
155
+ ...(request.evaluation?.setupActions ?? []),
156
+ ...(request.evaluation?.finalActions ?? []),
157
+ ...(request.evaluation?.interactions.flatMap((interaction) => interaction.action ? [interaction.action] : []) ?? []),
158
+ ];
159
+ if (actions.length) {
160
+ if (!fixtures)
161
+ throw new Error('workflow fixture actions require the Eval-owned fixture root.');
162
+ await validateWorkflowFixtureActions(actions, fixtures);
163
+ }
164
+ return request;
139
165
  }
140
166
  function parseJsonObject(stdout, label) {
141
167
  let value;
@@ -295,6 +321,7 @@ function workflowConfig(request, workspace) {
295
321
  `features.multi_agent_v2.expose_spawn_agent_model_overrides=${String(request.multiAgent.exposeSpawnAgentModelOverrides)}`,
296
322
  `agents.max_depth=${request.multiAgent.maxDepth}`,
297
323
  `developer_instructions=${configValue(request.developerInstructions)}`,
324
+ ...(request.reasoningEffort ? [`model_reasoning_effort=${configValue(request.reasoningEffort)}`] : []),
298
325
  ...request.roles.flatMap((role) => [
299
326
  `agents.${role.name}.description=${configValue(role.description)}`,
300
327
  `agents.${role.name}.config_file=${configValue(path.join(workspace, role.configFile))}`,
@@ -1478,6 +1505,203 @@ function sandboxPolicy(request) {
1478
1505
  excludeSlashTmp: false,
1479
1506
  };
1480
1507
  }
1508
+ function workflowFixtureRunner(input, control) {
1509
+ return (action) => runWorkflowFixtureAction({
1510
+ action, workspace: input.input.workspacePath, fixtures: input.input.fixturesPath,
1511
+ output: input.input.outputDir, evalRoot: input.evalRoot, sourceEnv: input.sourceEnv, runtime: input.codexRuntime,
1512
+ timeoutMs: control.remaining('workflow fixture action'), signal: control.signal,
1513
+ processManagement: input.processManagement,
1514
+ ...(input.processTree ? { processTree: input.processTree } : {}),
1515
+ ...(input.protectedEvalRoots ? { protectedEvalRoots: input.protectedEvalRoots } : {}),
1516
+ });
1517
+ }
1518
+ async function readInstructionV2File(root, relative, limit) {
1519
+ relativeWorkspacePath(relative, 'instruction-v2 capture path');
1520
+ let target = root;
1521
+ for (const part of relative.split('/')) {
1522
+ target = path.join(target, part);
1523
+ if ((await fs.lstat(target)).isSymbolicLink())
1524
+ throw new Error('instruction-v2 capture must not contain symbolic links.');
1525
+ }
1526
+ const stat = await fs.lstat(target);
1527
+ if (!stat.isFile() || stat.size > limit)
1528
+ throw new Error('instruction-v2 capture is not a bounded regular file.');
1529
+ return fs.readFile(target, 'utf8');
1530
+ }
1531
+ const instructionV2Transport = {
1532
+ async run(input) {
1533
+ const selected = input.request.transport;
1534
+ if (!path.isAbsolute(input.codexBin))
1535
+ throw new Error('instruction-v2 requires the Engine-resolved absolute Codex executable.');
1536
+ const control = new CodexEvalAttemptControl({
1537
+ label: 'instruction-v2 workflow', timeoutMs: input.timeoutMs,
1538
+ ...(input.signal ? { signal: input.signal } : {}),
1539
+ });
1540
+ const temporaryRoot = await createCodexEvalTemporaryRoot('instruction-v2-workflow-');
1541
+ const runtimeRoot = await fs.realpath(temporaryRoot.root);
1542
+ const codexHome = path.join(runtimeRoot, 'codex-home');
1543
+ const runDirectory = path.join(runtimeRoot, 'run');
1544
+ const env = {
1545
+ ...buildEngineEnvironment(input.sourceEnv, runtimeRoot, codexHome, input.codexRuntime),
1546
+ AW_SUBAGENT_RUN_DIR: runDirectory,
1547
+ AW_SUBAGENT_RESULT_FILE: path.join(runDirectory, 'result.json'),
1548
+ AW_SUBAGENT_TRACE_FILE: path.join(runDirectory, 'codex-events.jsonl'),
1549
+ AW_SUBAGENT_CODEX_BINARY: input.codexBin,
1550
+ };
1551
+ const startedAt = Date.now();
1552
+ let cleaned = false;
1553
+ let judgeNumber = 0;
1554
+ const judgeUsage = [];
1555
+ const grader = input.request.evaluation ? createCodexGrader({
1556
+ sourceEnv: input.sourceEnv, codexRuntime: input.codexRuntime, codexBin: input.codexBin,
1557
+ ...(input.request.model ? { model: input.request.model } : {}), processManagement: input.processManagement,
1558
+ }) : undefined;
1559
+ const evaluation = input.request.evaluation ? new WorkflowEvaluation(input.request.evaluation, input.input.workspacePath, async (prompt) => {
1560
+ const outputDir = path.join(input.input.outputDir, 'workflow-judges', String(++judgeNumber));
1561
+ await fs.ensureDir(outputDir);
1562
+ const result = await grader({ prompt, outputDir, timeoutMs: control.remaining('external semantic check'), signal: control.signal });
1563
+ if (!result.mechanicalPassed)
1564
+ throw new Error('external workflow semantic grader did not complete its read-only checks.');
1565
+ judgeUsage.push(result.usage);
1566
+ return parseConversationGraderOutput(result.output);
1567
+ }, workflowFixtureRunner(input, control)) : undefined;
1568
+ try {
1569
+ await Promise.all([
1570
+ fs.ensureDir(env.HOME), fs.ensureDir(env.TMPDIR), fs.mkdir(runDirectory, { mode: 0o700 }),
1571
+ materializeCodexEvalRuntimeHome(input.codexRuntime, codexHome, { trustedProject: input.input.workspacePath }),
1572
+ ]);
1573
+ await evaluation?.runSetupActions();
1574
+ const before = await snapshotWorkspace(input.input.workspacePath);
1575
+ let containment;
1576
+ if (input.request.evaluation) {
1577
+ containment = hostReadContainmentProfile({
1578
+ forbiddenHostRoots: [input.input.fixturesPath, input.input.outputDir],
1579
+ writableRoots: [runtimeRoot, input.input.workspacePath],
1580
+ unwritableRoots: input.request.sandbox === 'read-only' ? [input.input.workspacePath] : [],
1581
+ }) + '\n' + [...new Set([input.evalRoot, ...(input.protectedEvalRoots ?? [])])].map((root) => `(deny file-read* (require-all (subpath ${JSON.stringify(root)}) (require-not (subpath ${JSON.stringify(input.input.workspacePath)}))))`).join('\n');
1582
+ for (let ancestor = path.dirname(input.input.workspacePath); ancestor !== path.dirname(ancestor); ancestor = path.dirname(ancestor)) {
1583
+ containment += `\n(allow file-read-metadata (literal ${JSON.stringify(ancestor)}))`;
1584
+ }
1585
+ await assertHostReadContainmentAvailable(containment);
1586
+ }
1587
+ evaluation?.observe({ method: 'evaluation/externalSubmission', runId: selected.request.run.id }, null);
1588
+ const processResult = await runProviderProcess({
1589
+ executable: containment ? '/usr/bin/sandbox-exec' : selected.command[0],
1590
+ args: containment ? ['-p', containment, ...selected.command] : [...selected.command.slice(1)],
1591
+ cwd: input.input.workspacePath, env, stdin: JSON.stringify(selected.request),
1592
+ timeoutMs: control.remaining('external execution'), maxBufferBytes: MAX_ADAPTER_OUTPUT_BYTES,
1593
+ signal: control.signal,
1594
+ }, {}, {
1595
+ containment: input.processManagement.mode === 'unmanaged-confirmed' ? 'unmanaged-confirmed' : 'verified-observed',
1596
+ ...(input.processManagement.mode === 'unmanaged-confirmed' ? {} : { processTree: input.processTree }),
1597
+ });
1598
+ let receipt = null;
1599
+ let diagnostic = null;
1600
+ try {
1601
+ receipt = parseInstructionV2Receipt(JSON.parse(await readInstructionV2File(runDirectory, 'result.json', 64 * 1024)));
1602
+ }
1603
+ catch (error) {
1604
+ diagnostic = `External result receipt unavailable: ${error.message}`;
1605
+ }
1606
+ const trace = [];
1607
+ if (receipt?.traceFile) {
1608
+ try {
1609
+ const raw = await readInstructionV2File(runDirectory, receipt.traceFile, 8 * 1024 * 1024);
1610
+ const lines = raw.split(/\r?\n/u).filter((line) => line.trim());
1611
+ if (lines.length > 5_000)
1612
+ throw new Error('instruction-v2 trace event limit exceeded.');
1613
+ for (const line of lines)
1614
+ trace.push(JSON.parse(line));
1615
+ }
1616
+ catch (error) {
1617
+ diagnostic ??= `External trace unavailable: ${error.message}`;
1618
+ trace.length = 0;
1619
+ }
1620
+ }
1621
+ const completed = receipt?.outcome === 'completed' && receipt.providerTerminal === 'turn.completed'
1622
+ && receipt.instructionChannel !== 'user-prefix'
1623
+ && receipt.instructionsRevision === selected.request.instructions.revision
1624
+ && processResult.exitCode === 0 && processResult.signal === null
1625
+ && !processResult.timedOut && !processResult.cancelled && !processResult.maxBufferExceeded
1626
+ && receipt.generationStarted !== false && receipt.scopeViolation !== true
1627
+ && (receipt.localExitCode === undefined || receipt.localExitCode === 0)
1628
+ && (input.processManagement.mode === 'unmanaged-confirmed' || processResult.gradingEvidenceAccepted)
1629
+ && processResult.stdout.trim() !== '';
1630
+ const status = completed ? 'completed' : receipt?.outcome === 'failed'
1631
+ && (receipt.providerTerminal === 'turn.failed' || receipt.generationStarted === false) ? 'failed' : 'unknown';
1632
+ if (!completed)
1633
+ diagnostic ??= 'External execution lacks compatible channel, instruction revision, terminal, normal exit, or final text evidence.';
1634
+ const after = await snapshotWorkspace(input.input.workspacePath);
1635
+ await temporaryRoot.cleanup();
1636
+ cleaned = true;
1637
+ const usage = standardUsage(receipt?.usage);
1638
+ const evidence = {
1639
+ version: 1, rootThreadId: receipt?.providerSessionId ?? selected.request.run.id,
1640
+ completion: { status, turnStatus: receipt?.providerTerminal ?? 'unknown', durationMs: Date.now() - startedAt },
1641
+ termination: {
1642
+ reason: status,
1643
+ providerProcessCleanupConfirmed: input.processManagement.mode !== 'unmanaged-confirmed'
1644
+ && processResult.gradingEvidenceAccepted,
1645
+ },
1646
+ usage, finalMessage: processResult.stdout, workspaceDiff: workspaceDiff(before, after),
1647
+ skillLoads: [], threadGraph: [], collaborationCalls: [], leafCalls: [],
1648
+ cleanup: { isolatedCodexHomeRemoved: true },
1649
+ transport: {
1650
+ type: 'instruction-v2', request: selected.request, command: selected.command, receipt,
1651
+ traceAvailable: trace.length > 0,
1652
+ observationLimits: [
1653
+ 'Provider JSONL events are captured as supplied; complete file-access attribution and nested delegation coverage are not established.',
1654
+ 'A file diff proves observed final content, not absence of intermediate writes. Model statements do not prove read-only behavior, independent history, or OS isolation.',
1655
+ 'Provider trace is read after process exit. Artifact snapshots measure before submission and after cleanup; replayed event order does not supply intermediate snapshots.',
1656
+ ],
1657
+ diagnostic, exitCode: processResult.exitCode, signal: processResult.signal, stderr: processResult.stderr,
1658
+ },
1659
+ };
1660
+ if (completed && evaluation) {
1661
+ for (const frame of trace)
1662
+ evaluation.observe({ method: 'evaluation/externalTraceCapture', capturedAfterExit: true, frame }, evidence.rootThreadId);
1663
+ const messages = [
1664
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: selected.request.task }] } },
1665
+ ...trace,
1666
+ { type: 'response_item', payload: { type: 'agent_message', text: processResult.stdout } },
1667
+ ];
1668
+ try {
1669
+ evidence.evaluation = await evaluation.finish(evidence, messages);
1670
+ if (!trace.length) {
1671
+ evidence.evaluation.complete = false;
1672
+ evidence.evaluation.checks = evaluation.request.checks.map(({ name }) => ({
1673
+ name, verdict: 'unknown', evidence: 'External provider tool trace is unavailable; behavior assertions cannot be established from final text.',
1674
+ }));
1675
+ }
1676
+ }
1677
+ catch (error) {
1678
+ evidence.evaluation = {
1679
+ complete: false, checks: [], observations: evaluation.observations,
1680
+ snapshots: evaluation.snapshots, decisions: evaluation.decisions,
1681
+ fixtureActions: evaluation.fixtureActions, measurementStart: evaluation.measurementStart,
1682
+ };
1683
+ await writeWorkflowArtifacts(input.input.outputDir, redactCodexEvalValue({ evidence, trace, modelOutput: processResult.stdout, usage }, input.codexRuntime));
1684
+ throw error;
1685
+ }
1686
+ const totals = [usage, ...judgeUsage].map(standardUsage);
1687
+ evidence.usage = totals.every((value) => value !== null) ? {
1688
+ inputTokens: totals.reduce((sum, value) => sum + value.inputTokens, 0),
1689
+ outputTokens: totals.reduce((sum, value) => sum + value.outputTokens, 0),
1690
+ } : null;
1691
+ }
1692
+ return redactCodexEvalValue({ evidence, trace, modelOutput: processResult.stdout, usage: evidence.usage }, input.codexRuntime);
1693
+ }
1694
+ finally {
1695
+ try {
1696
+ if (!cleaned)
1697
+ await temporaryRoot.cleanup();
1698
+ }
1699
+ finally {
1700
+ control.dispose();
1701
+ }
1702
+ }
1703
+ },
1704
+ };
1481
1705
  const defaultTransport = {
1482
1706
  async run(input) {
1483
1707
  const control = new CodexEvalAttemptControl({
@@ -1489,6 +1713,7 @@ const defaultTransport = {
1489
1713
  const runtimeRoot = await fs.realpath(temporaryRoot.root);
1490
1714
  const codexHome = path.join(runtimeRoot, 'codex-home');
1491
1715
  const env = buildEngineEnvironment(input.sourceEnv, runtimeRoot, codexHome, input.codexRuntime);
1716
+ env.AW_SUBAGENT_CODEX_BINARY = input.codexBin;
1492
1717
  let session;
1493
1718
  let cleanupConfirmed = false;
1494
1719
  let codexHomeRemoved = false;
@@ -1513,7 +1738,7 @@ const defaultTransport = {
1513
1738
  throw new Error('workflow semantic grader did not complete its read-only checks.');
1514
1739
  judgeUsage.push(result.usage);
1515
1740
  return parseConversationGraderOutput(result.output);
1516
- }) : undefined;
1741
+ }, workflowFixtureRunner(input, control)) : undefined;
1517
1742
  let removeHandler;
1518
1743
  let removeFailureHandler;
1519
1744
  try {
@@ -1524,6 +1749,7 @@ const defaultTransport = {
1524
1749
  trustedProject: input.input.workspacePath,
1525
1750
  }),
1526
1751
  ]);
1752
+ await evaluation?.runSetupActions();
1527
1753
  const before = await snapshotWorkspace(input.input.workspacePath);
1528
1754
  let containment;
1529
1755
  if (evaluation) {
@@ -1537,6 +1763,7 @@ const defaultTransport = {
1537
1763
  }
1538
1764
  await assertHostReadContainmentAvailable(containment);
1539
1765
  }
1766
+ const configOverrides = workflowConfig(input.request, input.input.workspacePath);
1540
1767
  session = new EvalAppServerSession({
1541
1768
  codexBin: input.codexBin,
1542
1769
  cwd: input.input.workspacePath,
@@ -1545,7 +1772,7 @@ const defaultTransport = {
1545
1772
  sensitiveEnvironmentNames: input.codexRuntime.sensitiveEnvironmentNames,
1546
1773
  isolation: 'standard',
1547
1774
  processManagement: input.processManagement,
1548
- configOverrides: workflowConfig(input.request, input.input.workspacePath),
1775
+ configOverrides,
1549
1776
  ...(containment ? { hostReadContainmentProfile: containment } : {}),
1550
1777
  });
1551
1778
  await session.initialize(control.rpcTimeout('initialize'));
@@ -1723,6 +1950,13 @@ const defaultTransport = {
1723
1950
  collaborationCalls: rollout.collaborationCalls,
1724
1951
  leafCalls: rollout.leafCalls,
1725
1952
  cleanup: { isolatedCodexHomeRemoved: true },
1953
+ launch: {
1954
+ instructionChannel: 'developer', developerInstructions: input.request.developerInstructions,
1955
+ instructionsRevision: createHash('sha256').update(input.request.developerInstructions).digest('hex'),
1956
+ model: input.request.model, reasoningEffort: input.request.reasoningEffort ?? null,
1957
+ sandbox: input.request.sandbox, roles: input.request.roles, configOverrides,
1958
+ hostRoleAdoption: 'unknown',
1959
+ },
1726
1960
  };
1727
1961
  if (evaluation) {
1728
1962
  // Subject processes are stopped before final snapshots and independent grading.
@@ -1733,6 +1967,7 @@ const defaultTransport = {
1733
1967
  evidence.evaluation = {
1734
1968
  complete: false, checks: [], observations: evaluation.observations,
1735
1969
  snapshots: evaluation.snapshots, decisions: evaluation.decisions,
1970
+ fixtureActions: evaluation.fixtureActions,
1736
1971
  measurementStart: evaluation.measurementStart,
1737
1972
  };
1738
1973
  await writeWorkflowArtifacts(input.input.outputDir, {
@@ -1868,13 +2103,13 @@ export async function executeCodexWorkflowProvider(options) {
1868
2103
  control.throwIfStopped('prepare');
1869
2104
  throw error;
1870
2105
  }
1871
- const request = await parseCodexWorkflowRequest(prepared, options.provider.allowedModels, paths.workspacePath);
1872
- if (request.model === null && request.multiAgent.protocol === 'v1') {
2106
+ const request = await parseCodexWorkflowRequest(prepared, options.provider.allowedModels, paths.workspacePath, paths.fixturesPath);
2107
+ if (!request.transport && request.model === null && request.multiAgent.protocol === 'v1') {
1873
2108
  request.model = 'gpt-5.6-luna';
1874
2109
  }
1875
2110
  let execution;
1876
2111
  try {
1877
- execution = await (options.transport ?? defaultTransport).run({
2112
+ execution = await (options.transport ?? (request.transport ? instructionV2Transport : defaultTransport)).run({
1878
2113
  request,
1879
2114
  input: { ...options.input, ...paths, timeoutMs: control.remaining('Codex execution') },
1880
2115
  evalRoot: paths.evalRoot,
@@ -1883,6 +2118,7 @@ export async function executeCodexWorkflowProvider(options) {
1883
2118
  sourceEnv,
1884
2119
  timeoutMs: control.remaining('Codex execution'),
1885
2120
  processManagement,
2121
+ ...(options.processTree ? { processTree: options.processTree } : {}),
1886
2122
  ...(options.protectedEvalRoots ? { protectedEvalRoots: options.protectedEvalRoots } : {}),
1887
2123
  signal: control.signal,
1888
2124
  });
@@ -1891,13 +2127,16 @@ export async function executeCodexWorkflowProvider(options) {
1891
2127
  control.throwIfStopped('Codex execution');
1892
2128
  throw error;
1893
2129
  }
1894
- control.throwIfStopped('evidence collection');
1895
2130
  const sanitized = redactCodexEvalValue(execution, options.codexRuntime);
1896
2131
  const usage = standardUsage(sanitized.usage);
1897
- if (!usage || !standardUsage(sanitized.evidence.usage)) {
2132
+ if (!request.transport && (!usage || !standardUsage(sanitized.evidence.usage))) {
1898
2133
  throw new Error('codex-workflow completed without required token usage evidence.');
1899
2134
  }
1900
2135
  await writeWorkflowArtifacts(paths.outputDir, sanitized);
2136
+ control.throwIfStopped('evidence collection');
2137
+ if (sanitized.evidence.completion.status !== 'completed') {
2138
+ throw new Error(`instruction-v2 workflow execution is ${sanitized.evidence.completion.status}; no grade was accepted.`);
2139
+ }
1901
2140
  const beforeGrade = await artifactDigests(paths.outputDir);
1902
2141
  let gradeValue;
1903
2142
  try {