OpenProgram · Provider / Runtime design · implemented and reviewed · 2026-08-12
JSON Schema Structured Output
Design baseline: main@708bfbab. Implementation evidence: Tasks 1–6; final review commit 2fb471b3. Sections 2–15 retain the approved design and implementation checklist; §16 separates implemented contracts, verification evidence and unmet complete gates.
Design conclusion: Continue the existing Runtime.exec(..., response_format=...) as the sole public entry. Add normalized schema configuration to public provider types. Capability negotiation selects native provider JSON Schema or a constrained hidden-tool fallback. Regardless of provider strict claims, final results require strict JSON parsing and local validation against the caller’s original schema. Failure permits at most one semantic repair request; return only validated Python JSON values.
The existing Agent loop now implements schema normalization, negotiation, strict local validation, one optional repair, typed CLI/Web entries, record/replay v2 and the session-title consumer. Unverified provider combinations fail closed; ordinary text calls retain return semantics.
1. Scope, goals and boundaries
Function-tool arguments use the canonical tool schema for local validation. Strict provider export represents optional fields as required nullable fields; shared argument normalization restores those added nulls to omitted object properties before canonical validation. Required fields and originally nullable values retain their semantics. Only nulls accepted by the actual strict property schema are eligible, so enum, conditional and combinator constraints remain enforced. Normalization copies inputs, preserves array positions and applies the same optional-default semantics to non-strict callers. It does not modify function signatures or weaken canonical schemas.
Unified semantics
Python Runtime, Agent, CLI and WebSocket/API share one schema, negotiation result, validator and error classification.
Streaming compatibility
Preserve incremental provider text/tool events while explicitly representing candidate attempts, discard, retry and final structured results.
Reproducibility
Record/replay captures normalized requests and every provider call; offline replay reruns identical parsing, validation and bounded retries.
Excluded: lossy schema-to-provider-subset rewriting, automatic Pydantic types, cross-language code generation, tokenwise incremental JSON AST, Web schema editor, or changed ordinary-text returns/events.
2. Baseline audit (main@708bfbab)
This section records project-start facts and gaps as the basis for design decisions; it does not describe the current state of 2fb471b3 .
| Layer | Current facts | Gap |
|---|---|---|
| Public Runtime | agentic_programming/runtime.py Already exposes response_format: dict and forwards it on the callable-model path. | _call_via_providers() does not use it; default provider calls silently fall back to ordinary text. |
| Provider types | StreamOptions has sampling, tool, transport and header fields; Model has reasoning/thinking/compat; AssistantMessage stores only text/tools/errors. | No output schema, model capability or parsed JSON fields. |
| Model catalog | models_dev.py Already normalizes and caches models.dev structured_output . | Model has no corresponding field; Pydantic construction discards it and the model list does not show the capability. |
| Agent loop | AgentLoopConfig Inherits shared stream options; each round may execute tools until an assistant message contains no tool call. | Reconstructing SimpleStreamOptions has no schema to forward, nor final-candidate validation/repair state. |
| Stream events | Provider events cover text/thinking/tool/done/error; dispatcher converts them to Web/CLI stream_event. | Cannot represent “candidate 1 invalid; clear and enter attempt 2” or “final structured value validated”. |
| CLI / Web / API | --print Finally prints strings; WS chat_response.result.content is a string; POST /api/function/{name} returns asynchronous acknowledgements. | Requests cannot carry schemas; successful results lack typed JSON and errors lack stable machine-readable codes. |
| Session title | Design documentation specifies {title:string} schema. | agent/dispatcher/titles.py Actually prompts “ONLY title text”, then cleans lines, quotes, prefixes and length. |
| Record/replay | Format v1 records model, context, options and raw provider events; replay compares fields strictly. | New default fields change shapes between old recordings and new requests; validation retries must preserve call-index order. |
Call and return chain
Schema must travel through this existing chain. Do not add a structured provider client bypassing the Agent loop, which would create separate tool-calling, deadline, record/replay, DAG and persistence semantics.
3. Public API and normalized types
Retain the parameter name response_format because it is already public and documented. Support bare JSON Schema and an envelope containing OpenProgram controls. Normalize exactly once.
JsonValue = None | bool | int | float | str | list["JsonValue"] | dict[str, "JsonValue"]
class JsonSchemaOutput(BaseModel):
type: Literal["json_schema"] = "json_schema"
schema: dict[str, Any] # Original caller schema; never mutate in place
name: str = "response" # ^[A-Za-z_][A-Za-z0-9_-]{0,63}$
description: str | None = None
strict: bool = True
fallback: Literal["auto", "none", "prompt"] = "auto"
max_validation_retries: Literal[0, 1] = 1
# Existing-call compatibility: a bare dict is the schema, not a provider payload.
Runtime.exec(content=..., response_format={"type":"object", ...}) -> JsonValue
Runtime.exec(content=..., response_format=JsonSchemaOutput(...)) -> JsonValue
Runtime.exec(content=..., response_format=None) -> str
- Use overloads for return types; ordinary text calls still return
str. Successful structured calls do not return JSON strings. async_exec,exec, AgentSession and dispatcher use the sameJsonSchemaOutput; provider-privateresponse_formatpayload.- is not accepted. Before any network request, invoke
validator_for(schema).check_schema(schema). Unknown meta-schemas or invalid schemas raiseStructuredOutputSchemaErrorwithout consuming a provider request. - Declare
pyproject.tomldirectly injsonschema>=4.23. An existing transitive lockfile dependency is not a core-feature contract.
Public provider fields
class Model(BaseModel):
structured_output: bool | None = None # models.dev: true / false / unknown
class StreamOptions(BaseModel):
...
response_format: JsonSchemaOutput | None = None
class AssistantMessage(BaseModel):
...
structured_output: JsonValue | None = None
structured_output_mode: Literal["native", "tool", "prompt"] | None = None
structured_output_attempt: int | None = None
None means unknown capability, not False. Raw JSON text remains in assistant text content for history, DAG, audit and replay; program calls return structured_output.
4. Capability negotiation
The model catalog says only whether the model claims structured-output support. API adapters must also specify payload dialect, streaming and tool-combination constraints; negotiation checks both.
class StructuredOutputCapabilities(BaseModel):
native: Literal["supported", "unsupported", "unknown"]
dialect: Literal["openai_chat", "openai_responses", "anthropic",
"google", "bedrock"] | None
streaming: bool
with_tools: bool
schema_profile: str # Provider-subset checker name
negotiate(model, api_provider, options, tools) -> StructuredOutputPlan
# plan.mode: native | tool | prompt
# plan.provider_schema: Semantically equivalent projected copy
# plan.original_schema: Used for final local validation
- Invalid schema: fail immediately.
- If the adapter explicitly supports the API, the model field is not
False, schema projects losslessly and current tools are supported, choosenative. fallback="auto"and the provider supports strict tool schemas, schema projects losslessly, andtool_choicedoes not prohibit/force another tool: choose a hidden tool.fallback="prompt": explicit prompt fallback guarantees only local validation, not provider-level strictness.- Otherwise raise
StructuredOutputUnsupportedErrorbefore sending, reporting provider, model, conflicting capabilities and fallback reasons.
openai-codex and gemini-subscription may reuse official model entries but use different backends. Without API-adapter contract tests, Model.structured_output=True alone must not enable native fields.Provider mappings and initial boundaries
| API | Native payload | Initial strategy | Combination constraints |
|---|---|---|---|
openai-completions | response_format={type:"json_schema", json_schema:{name,description,strict,schema}} | native | Check tools/parallel against compatibility profiles; do not assume complete OpenAI support for community endpoints. |
openai-responses | text.format={type:"json_schema", name,description,strict,schema} | native | Retain refusal/incomplete termination; fall back to hidden tools when capabilities are insufficient. |
azure-openai-responses | Responses text.format | native | Deployment model and API version must pass adapter probes/contracts, not model-name assumptions. |
anthropic-messages | output_config.format={type:"json_schema", schema} | native | Merge with existing output_config.effort without overwriting; refusal/max_tokens still require local failure classification. |
google-generative-ai | response_mime_type="application/json" + response_json_schema | native | Accept only the officially supported subset; mark tool combinations with_tools=False. |
bedrock-converse-stream | outputConfig.textFormat.structure.jsonSchema until contract tests pass; schema is a JSON string | native | The adapter decides supported models and citation conflicts; local validation still uses the original schema. |
openai-codex | No verified ChatGPT-backend contract | tool fallback | Native capability unknown; do not send private OpenAI Responses fields. |
gemini-subscription | Cloud Code Assist requests internally contain generationConfig, but compatibility is unverified | tool fallback | Enable native capability only after independent contract tests. |
External contract references: OpenAI Structured Outputs, Anthropic structured outputs, Google GenerateContent API, Amazon Bedrock structured outputs, Azure OpenAI structured outputs. These interfaces change; adapter contracts are required before enabling capabilities.
5. Native mode and fallbacks
Native schema
Adapters only map normalized configuration to provider payloads. Existing providers/_schema may supply traversal/dialect detection, but transformations must preserve semantics, such as key naming or JSON-string serialization. Dropping constraints, relaxing additionalProperties, rewriting unions or any other lossy transform reports the exact path and disables native mode. Final validation always uses the caller’s original schema.
Hidden-tool fallback
{
"name": "__openprogram_submit_json",
"description": "Submit the final response matching the required schema.",
"parameters": <provider_schema>,
"strict": true
}
- Send the hidden tool alongside caller tools. Ordinary tool calls execute normally and continue to the next round.
- The hidden tool must appear alone. Its arguments are the final candidate; do not execute it or generate
ToolResultMessage. - The fallback round sets
parallel_tool_calls=False. If the caller explicitly requests parallel execution,tool_choice="none"or forces another tool, negotiation fails rather than overriding settings. - Plain text without a hidden-tool call is classified as
missing_submissionand may enter one repair request.
Prompt fallback
Enable only when the caller explicitly sets fallback="prompt" : add a compact schema and JSON-only requirement to the system layer, then parse/validate final text identically. This is not strict and is never silently used in auto . This distinguishes provider constraints from purely local detection.
6. Streaming events and incremental text
Structured output does not incrementally validate schemas. Forward raw text deltas per attempt so consumers may display generated JSON; parse only after final text_end . Add two framework events and optional attempt metadata on text events to prevent invalid attempts becoming final messages.
EventTextStart/TextDelta/TextEnd.output_attempt: int | None = None
EventStructuredOutputRetry {
type: "structured_output_retry",
attempt: 1,
next_attempt: 2,
issues: [{code, path, schema_path, message}],
}
EventStructuredOutputEnd {
type: "structured_output_end",
attempt: 1 | 2,
mode: "native" | "tool" | "prompt",
value: JsonValue,
}
done , strictly parse and validate. On success emit structured_output_end, followed by exactly one done; stream EOF without raw done/error fails as incomplete rather than synthesizing success from a partial message.structured_output_retry. CLI/Web clear candidate buffers; the Agent loop starts another provider stream.error without done and do not persist an invalid final assistant message.Provider raw done is consumed by the Agent loop; expose only the structured transaction’s final done. Ordinary text retains its event sequence. CLI/TUI/Web safely ignore unknown events for rolling upgrades.
7. Strict parsing, validation and bounded retries
Parsing rules
- Direct text uses
json.loads(raw, parse_constant=reject_nonfinite); reject Markdown fences, surrounding explanations, trailing content,NaN/Infinity. - Parse hidden-tool arguments as complete JSON text too; never use
utils/json_parse.pypartial/forgiving parsing. - Use
jsonschema.validators.validator_for(original_schema)to create the validator, with a registry prohibiting external-resource access. Sort errors by instance path, schema path and message. - Schema preflight rejects all
$ref/$dynamicRef/$recursiveRefthat cannot resolve to embedded resources in the current schema. Fragment references and relative references defined by embedded$idremain valid, so Runtime, CLI, Web and replay never initiate DNS, HTTP or file reads for local validation. - Canonical schema JSON is limited to 1 MiB, 4096 container nodes, 8192 container edges and depth 100. Exceeding any limit returns stable
invalid_schema. - before provider calls. Fragment-only references allow local JSON Pointers/anchors; direct reference self/mutual cycles fail preflight to prevent validator recursion overflow. Finite recursive schemas advancing through instance structure remain supported.
- Transport at most 20 errors with messages of at most 500 characters each, avoiding copying complete sensitive output into logs or WS errors.
Retry states
| Failure | Consumes validation retry? | Handling |
|---|---|---|
| Invalid schema definition | No | Before request StructuredOutputSchemaError. |
| Unsupported provider/model or conflicting configuration | No | Before request StructuredOutputUnsupportedError. |
| JSON syntax, schema violation, missing hidden submission | Yes, at most once | Append the invalid assistant candidate and deterministic issue list to temporary repair context; request once more. |
| Refusal, content filter or safety block | No | StructuredOutputGenerationError(code="refusal"). |
max_tokens/incomplete, context length | No | Retain provider reason; the caller adjusts budgets. The framework does not enlarge token limits itself. |
| transport/rate limit/provider internal | No | Existing stream/exec retry still handles these; they do not consume validation retries, but every Runtime provider call consumes shared max_retries budget. |
| Cancellation or deadline | No | Propagate immediately without repair. |
Shared call budget
max_validation_retries and max_retries are two limits, not multiplicative loops. The former caps semantic repairs after initial-candidate failure; the latter caps total provider calls initiated at the Runtime boundary by one synchronous/asynchronous exec , including the initial call, semantic repair and calls following transport failures. Repair requires both an available validation retry and remaining shared call budget.
Therefore Runtime-boundary provider calls never exceed max_retries. For example, max_retries=1 causes an invalid first candidate to immediately return StructuredOutputValidationError; max_retries=2 with one default validation retry permits only initial call plus one repair. A repair transport failure already consumes budget; the outer layer cannot regain a full budget. Adapter/SDK transport retries remain governed by existing deadlines/transport settings, not validation retry counts.
structured_output_retry.attempt and next_attempt continues identifying structured candidate attempts, not transport attempts. Emit only after Runtime confirms remaining shared budget and is about to repair; never emit false retry events on exhaustion.
Repair prompts contain only failure type and bounded issues, without reinterpreting schemas or calling another LLM. Repair context lasts only for this exec; session history/final DAG keep only the valid final assistant, while model-call records retain both provider calls for audit.
8. Coexistence with tool calling
| Case | Behavior |
|---|---|
| Native schema without user tools | Apply native output format directly. |
Native schema + user tools; adapter with_tools=True | Each Agent round carries schema. User tools execute normally; validate terminal text from the round without tools. |
| Native schema + tools, combination unverified | Use hidden-tool fallback if auto permits it; otherwise fail preflight. |
| Tool fallback + user tools | Combine hidden submit and user tools. Ordinary tools may run multiple rounds; submit ends the transaction. |
| One response contains ordinary tools and submit | Reject the candidate and enter bounded repair; do not execute ordinary tools, avoiding side effects after finalization. |
| Caller forces a specific tool or disables tools | Do not override intent; use native if available, otherwise unsupported. |
Explicitly copy structured configuration during agent_loop._stream_assistant_response() Reconstructing SimpleStreamOptions . Final validation belongs at the Agent-loop no-tool terminal boundary, not duplicated inside provider parsers.
9. CLI, WebSocket/API and error semantics
Python API
value = runtime.exec(content=..., response_format=schema) # Python JsonValue
except StructuredOutputError as e:
e.code # invalid_schema | unsupported | invalid_json | validation_failed |
# missing_submission | refusal | incomplete
e.provider
e.model
e.attempts
e.issues # bounded machine-readable list
StructuredOutputError uses the existing LLM-error boundary without pretending to be a transport error.retryable means whether a new exec/budget could succeed; validation-retry exhaustion is indicated by attempts .
CLI
Add to existing one-shot openprogram --print the option --json-schema PATH; initially leave interactive REPL/TUI input unchanged. Success writes canonical UTF-8 compact JSON plus newline to stdout and exits 0. Errors go to stderr: usage/invalid schema exits 2, unsupported 3, generation/final-validation failure 4. Existing provider/auth/cancel semantics remain. Persist compact JSON in session assistant content with parsed value/mode in metadata.
WebSocket and asynchronous HTTP API
// request: existing chat action and POST /api/function/{name} body
response_format?: JsonSchemaOutput | JSONSchema
// stream
{type:"chat_response", data:{type:"stream_event", event:{
type:"structured_output_retry", attempt:1, next_attempt:2, issues:[...]}}}
// success
{type:"chat_response", data:{type:"result", content:"{...}",
structured_output:<JsonValue>, structured_output_mode:"native", attempt:1}}
// terminal error
{type:"chat_response", data:{type:"error", code:"validation_failed",
content:"Structured output failed validation", attempts:2, issues:[...]}}
POST /api/function/{name}Continue returning{session_id,msg_id}acknowledgements immediately; deliver results through the same WS/event chain instead of synchronous HTTP.- Web chat sends no schema by default. Display schema results as canonical JSON text, retaining typed values for API consumers; retry clears candidate text.
- Server
json.dumps(..., default=str)must not serialize structured values; validated values already belong to the JSON data model, otherwise this is an internal error.
10. Record/replay and persistence
RECORDING_FORMAT_VERSIONmoves from 1 to 2. v2 request options record complete normalizedresponse_format; explicitly reject old v1 to avoid ambiguous mismatches from defaultNonefields.- Record raw provider events, not locally derived validation events. Replay returns identical raw events and the Agent loop deterministically reconstructs retry/end/error.
- Each validation repair creates a new provider call and
call_index; recordings must contain two request/call_end pairs. Missing or extra replay calls are mismatches. - After normalization, recursively sort schema keys and compute SHA-256 for logs, DAG metadata and error correlation. Recordings retain the full schema for strict comparison; hashes do not replace it.
- Session assistant content stores final canonical JSON; metadata stores mode, attempt and schema hash. Invalid candidates never enter the session message chain.
11. Session-title migration
After core capabilities are ready, migrate agent/dispatcher/titles.py as the first internal consumer:
TITLE_SCHEMA = {
"type": "object",
"properties": {"title": {"type": "string", "minLength": 1, "maxLength": 80}},
"required": ["title"],
"additionalProperties": false,
}
result = runtime.exec(content=prompt, toolset="none", max_iterations=2,
response_format=JsonSchemaOutput(
schema=TITLE_SCHEMA, name="session_title", max_validation_retries=1,
))
title = result["title"]
Retain Unicode trimming and final database length protection; remove ONLY-title-text instructions and quote, Title: and first-line format cleanup. Title generation stays best-effort: structured errors retain the phase-1 placeholder without affecting the main turn. Update session/name.md and feature matrix to distinguish implemented from designed behavior.
12. References: adopt, modify, reject
Repository references/pi-ai already has shared stream options, provider-local payload builders, tool schemas and supportsStrictMode, but strict applies only to tool definitions. It lacks generic response schemas, final JSON validation and structured retries.
| Decision | Source/current state | Treatment in this design |
|---|---|---|
| Adopt | Forward shared options/types through every provider adapter. | JsonSchemaOutput belongs in public types; payload mapping stays provider-local. |
| Adopt | Tool strictness is provider/model compatibility, not a unilateral caller promise. | Hidden-tool fallback is constrained by negotiation and schema profiles. |
| Modify | pi-ai keeps strict capability in compat; models.dev already supplies structured_output. | Use a tri-state model field plus independent API-adapter capability; decide jointly. |
| Modify | Existing _schema can downgrade tool schemas for providers. | Structured output permits only lossless projection; always validate locally against the original schema. |
| Reject | Rely only on provider strict and return raw text. | Rejected: refusal, truncation, compatible endpoints and provider bugs require local parse + validate. |
| Reject | Repair final output using forgiving/partial JSON parsers. | Rejected: accept only one complete JSON document; failures enter bounded semantic repair. |
| Reject | Parse every JSON delta into incremental objects and publish them. | Rejected: schema validation is defined only for complete candidates; streaming carries candidate text and attempt only. |
| Reject | Create an independent structured-output provider path. | Rejected: reuse Agent loop, tools, deadlines, DAG and record/replay. |
13. Exact files to change
| File | Modify |
|---|---|
| openprogram/providers/types.py | Add JsonSchemaOutput, JsonValue, model tri-state, StreamOptions fields, AssistantMessage parsed fields and structured retry/end events. |
| openprogram/providers/api_registry.py openprogram/providers/register.py | Register adapter structured capabilities; provide unknown-safe lookup without guessing from model names. |
| openprogram/providers/structured_output.py new | Input normalization, preflight, negotiation, strict parsing, local validation, bounded issues, schema hash and error types. |
| openprogram/providers/_schema/* | Add lossless/lossy reporting and provider schema profiles; retain original schemas. |
| openprogram/providers/sources/models_dev.py openprogram/providers/enabled_models.py apps/server/openprogram_server/_webui/_model_listing/listing.py | Retain, construct and expose structured_output tri-state. |
| openprogram/providers/openai_completions/openai_completions.py openprogram/providers/openai_responses/openai_responses.py openprogram/providers/azure_openai_responses/azure_openai_responses.py | Map Chat/Responses/Azure native payloads and handle refusal/incomplete. |
| openprogram/providers/anthropic/anthropic.py | Merge format into existing output_config while retaining adaptive-thinking effort. |
| openprogram/providers/google/google.py openprogram/providers/google_gemini_cli/google_gemini_cli.py | Official Google adapter maps native fields; subscription adapter initially uses explicit unknown/tool fallback. |
| openprogram/providers/amazon_bedrock/amazon_bedrock.py openprogram/providers/openai_codex/openai_codex.py | Bedrock maps outputConfig; Codex adapter initially uses explicit unknown/tool fallback. |
| openprogram/agent/types.py openprogram/agent/agent_loop.py openprogram/agent/internals/_event_parsing.py | Forward schemas; implement no-tool terminal validation, hidden tools, one repair, derived events and final parsed messages. |
| openprogram/agentic_programming/runtime.py openprogram/providers/callable_model.py | Fix silent provider-path omission; add overloads/parsed returns and identical final validation for callable models. |
| openprogram/providers/recording.py openprogram/providers/replay.py | Format v2, normalized option comparison and multi-call validation-retry tests; raw provider event set unchanged. |
| apps/cli/python/openprogram_cli/_impl/commands/chat.py apps/cli/python/openprogram_cli/_impl/application.py apps/cli/python/openprogram_cli/_impl/chat.py apps/cli/python/openprogram_cli/_impl/repl/turn.py | One-shot schema argument, typed result, stdout/stderr/exit codes and session persistence. |
| apps/server/openprogram_server/_webui/ws_actions/chat.py apps/server/openprogram_server/_webui/routes/chat.py apps/server/openprogram_server/_webui/_execute/chat.py apps/server/openprogram_server/server.py | Request validation, schema forwarding and structured stream/result/error envelopes. |
| apps/cli/src/ws/client.ts apps/cli/src/screens/repl/wsHandlers/handleChatResponse.ts apps/cli/src/screens/repl/wsHandlers/streamingHelpers.ts | TS request/event types, candidate clearing on retry and typed-result reception; no schema editor. |
| openprogram/agent/dispatcher/titles.py | Migrate title consumer, remove format cleanup and retain length/best-effort boundaries. |
| pyproject.toml uv.lock | Direct dependency on jsonschema and update lockfile. |
| docs/reference/design/runtime/session/name.md docs/reference/design/feature-matrix.html | After implementation, update documentation status and actual contracts. |
14. Test matrix and acceptance
| Layer | Required coverage | Suggested files |
|---|---|---|
| Normalization/validator | Bare schema, envelope, invalid meta-schema, object/array/scalar/null, non-finite values, fences/trailing text, stable issue paths; zero-network remote refs, local direct-reference cycles and global 1 MiB/4096 nodes/8192 edges/100 depth budgets. | tests/component/providers/adapters/test_structured_output.py (new) |
| Negotiation | true/false/unknown × adapter support; lossy schemas; fallback none/auto/prompt; tool_choice/parallel conflicts. | Above plus test_provider_meta.py |
| Provider payload | Per-adapter payload snapshots; Anthropic effort+format merge; Bedrock schema JSON string; unknown adapters send no private fields. | Existing tests/component/providers/adapters/test_openai.py, test_anthropic.py, test_gemini.py + new Bedrock cases |
| Streaming | One-attempt success; invalid delta→retry→clear→success; second failure without done; no retry on cancellation/deadline; no synthesized success for EOF without raw terminal. | tests/providers/test_structured_output_streaming.py (new) |
| Tools | Native+tools; hidden tool alone; ordinary multiround tools then submit; reject ordinary+submit together; forced/none/parallel conflicts. | tests/unit/programs/runtime/test_tools_runtime.py + new Agent loop cases |
| Runtime | No schema returns str; schema returns JsonValue; default provider forwards it; callable models validate locally; sync/async prove initial, repair and transport-retry calls share max_retries budget. | tests/component/programs/tools/test_runtime_structured_output.py (new) |
| Record/replay | v2 single call, two-call validation retry, schema-mismatch paths, v1 rejection and identical offline derived events. | tests/component/providers/registry/test_record_replay.py |
| CLI | Successful stdout is JSON only; exit 2/3/4; no full candidates on stderr; ordinary --print unchanged. | tests/component/providers/adapters/test_cli.py + CLI integration |
| WS/API | Request schema, retry event, typed result, machine error, foreign-session routing and old-client field ignoring. | tests/unit/webui/test_webui_chat_dispatcher.py + TS tests |
| Session title | Valid title, structured-failure placeholder, Unicode/80-character boundaries and no old regex dependency. | tests/unit/context/test_dispatcher_compaction_title.py |
| Regression | All provider streams, tool loops, ordinary chat, DAG, session persistence, typecheck and docs HTML. | Existing suites + npm test/npm run typecheck |
max_retries and semantic repairs do not exceed max_validation_retries; ordinary text recordings, events and return types remain unchanged.15. Implementation order and completion
- Public types, direct dependency, schema preflight/validator/errors; establish pure unit tests first.
- Capability registry, models.dev tri-state retention and provider schema profiles.
- Provider payload mappings and adapter contracts; unverified adapters stay unknown.
- Agent-loop terminal validation, hidden tools, events and one repair; Runtime parsed returns.
- Record/replay v2, CLI and WS/API consumers; verify old text behavior unchanged.
- Migrate session titles; update feature matrix and naming documentation.
Provider capabilities, contracts and title consumer are implemented. Feature matrix retains the user-approved score 78.5 and existing status cells; complete regression gates and specific trust-boundary evidence remain separately recorded here. Adding payload fields alone is not feature completion.
16. Implementation and verification evidence
| Scope | Reviewed implementation | Boundary |
|---|---|---|
| Public contract and providers | JsonSchemaOutput Normalization/preflight; native/hidden-tool/explicit-prompt negotiation against a registry snapshot; verified OpenAI, Anthropic, Google and Bedrock mappings; final local validation against the caller schema. | Unverified adapters, models without explicit opt-in, lossy schema projection or tool-control conflicts never send unverified native payloads; fallback="prompt" must be explicitly requested. |
| Agent / Runtime | The Agent loop owns negotiation, typed retry/end events, at most one validation repair and parsed terminal; Runtime.exec/async_exec returns validated Python JSON values, while ordinary calls return text. | Invalid candidates do not enter final assistant messages; cancellation, deadlines, refusal and incomplete do not become repairs. |
| Entries and reproduction | CLI --json-schema PATH|-, Web typed lifecycle, recording v2 and strict offline replay share normalization; structured one-shot CLI stdout has exactly one JSON document. | No Web schema editor, cross-language generation or incremental JSON AST; explicitly reject v1 structured recordings while retaining ordinary-v1 compatibility. |
| Session title | Default Agent provider/model, toolset="none", at most two model iterations and one schema repair; accepts only {title:string}, with only Unicode trim and 80-code-point storage protection in the consumer. | Setup, exec and close failures are best-effort and log only fixed lifecycle labels; provider exception bodies and tracebacks never enter logs. |
Commit evidence: Task 1 00945c85..27a5aa8a; Task 2 da4ececd..8ca7b04c; Task 3 6f8c80b7..546c2bc7; Task 4 27e4764f..510f60ee; Task 5 9743f3e1..86ca66d5; Task 6 5bba3e64..2fb471b3. Final scoped SPEC and QUALITY reviews for Tasks 1–6 are PASS with remaining 0.
Verification state: Task 6 affected selection: 219 passed, 1 skipped; Task 7 focused selection: 226 passed, 1 skipped; documentation build: 453 pages, link check: 0 broken. The complete Python selection on the final Task 7 tree produced 3290 passed, 5 skipped, 1 xfailed, 2 failed; the two failures were the existing tool-cancellation ExecInterrupt assertion and memory-writer turn-order assertion, independently reproduced before implementation at baseline 86ca66d5 . They are not attributed to this structured-output diff, but do not satisfy complete-gate release requirements.
Subsequent quality fixes: Global preflight now enforces 1 MiB/4096 nodes/8192 edges/100 depth budgets and rejects external resources/direct cycles. Local validation uses a no-retrieval registry; structured streams commit only after a provider raw terminal. Scoped SPEC/QUALITY conclusions will be recorded after review.
Repository evidence: openprogram/providers/types.py, sources/models_dev.py, seven provider adapters, agent/agent_loop.py, agentic_programming/runtime.py, agent/internals/_event_parsing.py, CLI/WebSocket chain, recording.py/replay.py, agent/dispatcher/titles.py, references/pi-ai. Line numbers are not tied to source snapshots; locate implementation by symbols/tests.
External provider documentation only confirms current payloads/constraints; OpenProgram adapter contracts remain the prerequisite for enabling capabilities.