Skip to content

feat(eval): enforce caller-supplied output schemas#5284

Open
vmcall wants to merge 6 commits into
can1357:mainfrom
vmcall:feat/strict-subagent-schema
Open

feat(eval): enforce caller-supplied output schemas#5284
vmcall wants to merge 6 commits into
can1357:mainfrom
vmcall:feat/strict-subagent-schema

Conversation

@vmcall

@vmcall vmcall commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • make caller-supplied eval agent(..., schema=...) contracts fail closed automatically, without exposing a model-facing strictness flag
  • keep task-agent native and inherited output schemas best effort so ordinary delegation can still return useful partial output
  • prepare and dereference schemas before dispatch, then preserve the internal enforcement policy through batching, follow-up turns, isolation, persistence, and cold revival
  • fail closed after bounded correction attempts with typed schema_violation results across JavaScript, Python, Ruby, and Julia eval helpers
  • preserve abort/timeout identity, provider and transport failures, legacy JavaScript positional calls, permissive task fallbacks, and eval cleanup/accounting

Verification

  • bun test — 243 passed, 3 skipped, 0 failed across 19 focused structured-output, executor, eval, persistence, and Vibe files; 927 assertions
  • bun run check in packages/coding-agent — passed
  • adversarial review findings resolved: malformed permissive-schema fallback, pre-yield execution-failure precedence, retry-budget reset, strict-only typed eval failures, unresolved $ref preflight, and already-aborted precedence

Closes #5278

@github-actions github-actions Bot added the vouched Passed the vouch gate label Jul 12, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3a3ebda422

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

stderr = "";
}
} else if (!hasOutputSchema && allowFallback && rawOutput.trim().length > 0) {
} else if (prepared.normalized === undefined && !prepared.error && allowFallback && rawOutput.trim().length > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow permissive malformed schemas to fall back

When a permissive subagent has a malformed output schema (for example the same empty-string schema that the yielded path treats as loose with only an invalid output schema warning), a clean run that writes raw output but misses yield now falls through to the hard missing-yield failure because this fallback requires prepared.error to be false, and resolveFallbackCompletion also returns null on that flag. This regresses the previous behavior where malformed schemas degraded to no validation for raw-output fallback; keep blocking this only for strict mode or treat schema-preparation errors as unconstrained in permissive mode.

Useful? React with 👍 / 👎.

@roboomp roboomp added agent Agent runtime planning and orchestration feat prompting Prompt templates and prompt assembly review:p2 sdk SDK and extension integration surface tool Tool behavior and integrations triaged labels Jul 12, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ffa5929bd7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +624 to +625
} else if (strict) {
reject("strict schema mode requires a valid yield submission", undefined);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve non-yield execution failures in strict mode

When a strict structured subagent exits nonzero before it can call yield (for example an auth/rate-limit/provider error that leaves exitCode === 1 and stderr populated, but is not doneAborted/signalAborted), this branch unconditionally replaces the original failure with schema_violation: strict schema mode requires a valid yield submission. The permissive path preserves pre-existing nonzero failures by only adding the missing-yield error when exitCode === 0; strict mode should similarly avoid clobbering the actionable stderr/raw output for failures that happened before schema enforcement could run.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b72b3be19

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +409 to 415
if (this.#schemaMode === "strict") {
return this.#strictFailure(
"schema validation failed",
data,
sectionFailure,
sectionValidation?.requiredFields ?? this.#requiredFields,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset schema retry budget after successful yields

In strict mode this fail-closed branch uses the cumulative #schemaValidationFailures counter, but the counter is never reset after a later valid yield. A subagent that makes two schema mistakes on one section, corrects it, then makes two mistakes on an independent section will hit schema_violation on the second section after only one retry, despite the prompt saying the budget is for consecutive correction attempts. Reset the counter after a successful validation so earlier corrected mistakes do not consume the strict budget for later yields.

Useful? React with 👍 / 👎.

@roboomp roboomp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: broad opt-in strict structured-output feature; maintainer sign-off still needed for the new eval/SDK contract surface.

Found one blocking behavior regression: the eval bridge maps any SingleResult.failure into AgentSchemaViolationError, so default permissive agent(..., schema=...) calls can now raise the strict-only typed error.

Verification: bun test ./test/tools/yield.test.ts ./test/tools/output-schema-validator.test.ts passed (65 tests). The broader eval-focused test command failed before assertions because src/export/html/index.ts could not resolve ./tool-views.generated.js in this worktree.

Thanks for the thorough coverage and clear strict/permissive split in the implementation.

})();

if (result.exitCode !== 0 || result.error || result.aborted) {
const strictSchemaFailure = result.failure !== undefined;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: result.failure is not strict-mode-only. finalizeSubprocessOutput() sets it for any parent-side schema rejection, including the default schemaMode: "permissive" fallback path when agent(prompt, { schema }) returns JSON that fails the schema. This branch then returns { schemaViolation }, and every language prelude raises AgentSchemaViolationError, so omitted schemaMode no longer has the documented permissive behavior. Gate this on schemaMode === "strict" (and let permissive failures continue through the existing ToolError/subprocess failure path).

@vmcall vmcall force-pushed the feat/strict-subagent-schema branch from 94feb17 to 9e4826c Compare July 12, 2026 19:32
vmcall added 5 commits July 13, 2026 09:25
- Added opt-in permissive and strict schema modes across subagent execution, eval runtimes, persistence, and follow-up turns.
- Failed strict invocations before dispatch or after bounded correction attempts with typed schema-violation diagnostics.
- Preserved permissive fallback behavior and covered assembly, abort, revival, and runtime boundaries.

Fixes can1357#5278
- Allowed permissive runs with unusable schemas to retain the legacy raw-output fallback.\n- Added regression coverage for malformed schemas without yield data.
- Kept provider and transport failures intact when strict subagents exit before yielding.\n- Added regression coverage preventing schema-violation relabeling.
- Reset consecutive schema retries after accepted data-bearing yields.\n- Routed typed eval schema failures only for strict invocations while retaining generic permissive errors.
- Made caller-supplied eval schemas fail closed without a model-facing mode argument.\n- Kept task-agent native and inherited handoff schemas permissive across nested spawns.\n- Removed schema mode from JavaScript, Python, Ruby, and Julia eval helper surfaces.
@vmcall vmcall force-pushed the feat/strict-subagent-schema branch from 9e4826c to 5a0d679 Compare July 13, 2026 07:35
@vmcall vmcall changed the title feat(task): add strict structured output enforcement feat(eval): enforce caller-supplied output schemas Jul 13, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a0d679a74

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}
} else if (!hasOutputSchema && allowFallback && rawOutput.trim().length > 0) {
} else if (
(prepared.normalized === undefined || prepared.error) &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve raw fallback for unconstrained schemas

When a subagent/session uses the valid boolean outputSchema: true contract and exits cleanly with plain text but no yield, prepared.normalized is true, resolveFallbackCompletion returns null for non-JSON text, and this branch no longer treats the run as loose. It falls through to the missing-yield failure even though absent schemas and malformed permissive schemas still accept non-empty raw output; include the normalized === true unconstrained case here so intentionally loose agents do not regress to exit code 1.

Useful? React with 👍 / 👎.

Accepted non-empty plain-text completion when a permissive task uses the valid boolean true JSON Schema and exits without yielding. Added regression coverage for the no-yield path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Agent runtime planning and orchestration feat prompting Prompt templates and prompt assembly review:p2 sdk SDK and extension integration surface tool Tool behavior and integrations triaged vouched Passed the vouch gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add fail-closed strict mode for structured subagent output

2 participants