|
| 1 | +--- |
| 2 | +'@objectstack/spec': minor |
| 3 | +--- |
| 4 | + |
| 5 | +feat(spec)!: an agent's `structuredOutput` is JSON-only — the `regex` / `grammar` / `xml` formats and the `coerce_types` step are retired, and the block is `live`, enforced by the cloud AI runtime (#21277) |
| 6 | + |
| 7 | +**BREAKING** — four members leave the agent's structured-output vocabulary: |
| 8 | +`regex`, `grammar` and `xml` from `StructuredOutputFormat` (so from |
| 9 | +`agent.structuredOutput.format` and `agent.structuredOutput.fallbackFormat`), and |
| 10 | +`coerce_types` from `TransformPipelineStep` (so from |
| 11 | +`agent.structuredOutput.transformPipeline`). ADR-0049 enforce-or-remove, ruled |
| 12 | +retire. The cloud AI runtime, the one runtime that executes agents, enforces |
| 13 | +`structuredOutput` on every final answer and refused an agent declaring any of the |
| 14 | +four before its first turn: the spec never had a key to carry the pattern or |
| 15 | +grammar a `regex` / `grammar` answer would be checked against, an answer is checked |
| 16 | +only as JSON, and no coercion engine exists. So no authored value of the four ever |
| 17 | +did what it named, and authoring now refuses them by name instead of the first |
| 18 | +live turn refusing the agent. `json_object`, `json_schema`, `trim`, `parse_json` |
| 19 | +and `validate` are unchanged. |
| 20 | + |
| 21 | +### FROM → TO |
| 22 | + |
| 23 | +| removed | what to write instead | |
| 24 | +| --- | --- | |
| 25 | +| `structuredOutput.format: 'regex'`, `'grammar'` or `'xml'` | `format: 'json_schema'` with a JSON Schema in `schema` when the answer must have a shape, or `format: 'json_object'`; or delete the `structuredOutput` block if the agent needs no output contract. | |
| 26 | +| `structuredOutput.fallbackFormat: 'regex'`, `'grammar'` or `'xml'` | `'json_object'` or `'json_schema'`, or delete the key. | |
| 27 | +| `'coerce_types'` in `structuredOutput.transformPipeline` | delete the step, and declare the exact types in `schema` so the answer is validated as the model wrote it. | |
| 28 | + |
| 29 | +**The one-line fix: use `json_schema` with a JSON Schema; drop `coerce_types`.** |
| 30 | +`os migrate meta --from 17` lists the mechanical edits for existing sources. |
| 31 | + |
| 32 | +Each retired member is refused at parse with a prescription naming the JSON |
| 33 | +formats, and in `tsc` (the members are gone from the `StructuredOutputFormat` / |
| 34 | +`TransformPipelineStep` types). Any other unknown value keeps zod's own message. |
| 35 | + |
| 36 | +### The retirement kit |
| 37 | + |
| 38 | +- **Value-level retirement.** Both enums are declared through |
| 39 | + `enumWithRetiredValues` (`shared/retired-key.ts`), the house mechanism for a |
| 40 | + narrowed vocabulary, with the prescriptions module-private. No authorable KEY and |
| 41 | + no def changed, so nothing lands in `RETIRED_KEYS_BY_MAJOR` and the four surface |
| 42 | + ratchets (`api-surface`, `authorable-surface`, `json-schema.manifest`, |
| 43 | + `api-surface-signatures`) are byte-identical. |
| 44 | +- **D2 conversion `agent-structured-output-refused-members-removed`** (step 18, |
| 45 | + retired from the load path): it deletes a `structuredOutput` block whose `format` |
| 46 | + was retired (the format is required, and no rewrite can say which JSON contract |
| 47 | + was meant), deletes a retired `fallbackFormat`, and drops `coerce_types` from the |
| 48 | + pipeline, keeping the other steps in order. Stored `sys_metadata` agent rows replay |
| 49 | + it at rehydration; one notice per edit. |
| 50 | +- **D3 entry `agent-structured-output-refused-members-retired`** carries the |
| 51 | + judgement the conversion cannot make: whether an agent whose block was deleted |
| 52 | + should now carry a `json_schema` contract. |
| 53 | +- **No deprecation window**, per the project's startup-stage posture. |
| 54 | + |
| 55 | +### Describes and the liveness ledger |
| 56 | + |
| 57 | +- `agent.structuredOutput` drops `[EXPERIMENTAL — not enforced]`: it states that the |
| 58 | + cloud AI runtime enforces it on every final answer and that the open framework |
| 59 | + edition does not run agents. Its ledger row moves `experimental` → `live`, citing |
| 60 | + the cloud readers (`agent-runtime.ts#compileStructuredOutput`, |
| 61 | + `ai-service.ts#AIService.settleFinalAnswer`) as attested by the cloud seat's |
| 62 | + reading at cloud `cb62c3ea`, `verifiedAt` 2026-10-02. `os lint` / `os validate` no |
| 63 | + longer warn `liveness-experimental-property` on an agent that sets it. |
| 64 | +- `fallbackFormat`'s describe states what the runtime does with it: once the primary |
| 65 | + format's retries are spent, the last answer is checked against the fallback. |
| 66 | +- `guardrails.blockedTopics`'s describe states the enforced match: an exact, |
| 67 | + case-sensitive match on the tool name, on `action_` plus the action type, or on |
| 68 | + the tool category. |
| 69 | +- The generated agent reference page follows. |
| 70 | + |
| 71 | +⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` is |
| 72 | +published, and tenant-authored agents were not measured. This repo authors no |
| 73 | +`structuredOutput` outside `packages/spec`, and the cloud seat's reading found no |
| 74 | +producer in cloud. |
| 75 | + |
| 76 | +Clause-②: no (narrowing) |
| 77 | + |
| 78 | +<!-- adr-0087: registered agent-structured-output-refused-members-removed, agent-structured-output-refused-members-retired --> |
0 commit comments