Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/21374-agent-structured-output-form-offer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@objectstack/spec": minor
"@objectstack/platform-objects": patch
---

The agent metadata form now offers `structuredOutput`, the output contract the cloud AI runtime enforces on every final answer. It is a `composite` row in the AI Configuration section, spelled like the `memory` and `guardrails` rows: Studio derives its seven sub-rows from the served JSON Schema.

Clause-②: no

- Before this, the block had no row on the agent form, so the only way to author it in Studio was the Source tab. The form's reconciliation test excused that with a ledger row saying the key was declared but not enforced. The key has been enforced since the structured-output enforcement landed (liveness `live`), and that row is gone.
- What Studio renders, read in the console's metadata form renderer: `format` and `fallbackFormat` are selects over `json_object` / `json_schema`. `strict` and `retryOnValidationFailure` are switches, and `maxRetries` is a number. `transformPipeline` is a multi-select over `trim` / `parse_json` / `validate`. `schema`, the free-form JSON Schema record, is a JSON text editor: the stored value is shown as JSON and saved back as parsed. That is the same editor the action form already gives `ai.outputSchema`, which is the other slot this JSON Schema rule governs.
- Two editing limits of those controls. A multi-select toggle stores the steps in the order the enum declares them (`trim`, `parse_json`, `validate`). And the schema editor keeps the last valid JSON while the text does not parse. A value nobody edits is saved back unchanged.
- No schema, parse or export change. The accept set of `AgentSchema` is unchanged, and so is the refusal of an untyped JSON subschema at `structuredOutput.schema`. What moves is the form payload `getMetaTypes()` serves, and the two new leaves of the `platform-objects` metadata-form catalogs (the row's label and help text). Those are authored in `zh-CN`, `ja-JP` and `es-ES`, not left as copies of the English source.
Original file line number Diff line number Diff line change
Expand Up @@ -2404,6 +2404,10 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
label: "Lifecycle",
helpText: "State machine defining conversation flow"
},
structuredOutput: {
label: "Structured Output",
helpText: "Output contract for the agent's final answer: JSON format, the JSON Schema it is checked against, retries, fallback format and transform steps. Enforced by the cloud AI runtime."
},
skills: {
label: "Skills",
helpText: "Skill names (Agent→Skill→Tool architecture)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2404,6 +2404,10 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
label: "Ciclo de vida",
helpText: "Máquina de estado que define el flujo de conversación"
},
structuredOutput: {
label: "Salida estructurada",
helpText: "Contrato de salida para la respuesta final del agente: formato JSON, el JSON Schema con el que se valida, reintentos, formato de respaldo y pasos de transformación. Lo aplica el runtime de IA en la nube."
},
skills: {
label: "Habilidades",
helpText: "Nombres de skill (arquitectura Agent→Skill→Tool)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2404,6 +2404,10 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
label: "ライフサイクル",
helpText: "会話フローを定義するステートマシン"
},
structuredOutput: {
label: "構造化出力",
helpText: "エージェントの最終回答に対する出力契約: JSON 形式、回答の検証に使う JSON Schema、リトライ、フォールバック形式、変換ステップ。クラウド AI ランタイムが適用します。"
},
skills: {
label: "スキル",
helpText: "スキル名(Agent→Skill→Tool アーキテクチャ)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,9 @@ describe('#19403 round 10 — the verdicts, on the live bundles', () => {
// three locales.
// 659 since the field form offers `useGrouping` on `number` fields: one
// new row label, authored in all three locales.
expect(translated.length, `${locale} positive control`).toBe(659);
// 660 since the agent form offers `structuredOutput`: one new row label,
// authored in all three locales.
expect(translated.length, `${locale} positive control`).toBe(660);
}
// ⭐ DARK — the blindness, executable. On a synthetic two-locale catalog the
// all-three predicate returns 0 while the per-locale one returns 1, so the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2404,6 +2404,10 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
label: "生命周期",
helpText: "定义会话流程的状态机"
},
structuredOutput: {
label: "结构化输出",
helpText: "代理最终回答的输出契约:JSON 格式、用于校验回答的 JSON Schema、重试、回退格式与转换步骤。由云端 AI 运行时强制执行。"
},
skills: {
label: "技能",
helpText: "技能名称(Agent→Skill→Tool 架构)"
Expand Down
1 change: 1 addition & 0 deletions packages/spec/src/ai/agent.form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const agentForm = defineForm({
{ field: 'planning', type: 'composite', helpText: 'Autonomous reasoning configuration (strategy, max iterations, replan)' },
{ field: 'memory', type: 'composite', helpText: 'Memory management (short-term, long-term, reflection)' },
{ field: 'lifecycle', type: 'composite', helpText: 'State machine defining conversation flow' },
{ field: 'structuredOutput', type: 'composite', helpText: "Output contract for the agent's final answer: JSON format, the JSON Schema it is checked against, retries, fallback format and transform steps. Enforced by the cloud AI runtime." },
],
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,13 +405,6 @@ const LEDGER: ReadonlyArray<OmitEntry | SubsetEntry> = [
key: 'requires',
why: 'declared, not enforced yet — liveness verdict `planned` (ADR-0080: inferred at compile time; save/load enforcement of plugin presence is deferred). No offer until it is enforced; whether to offer it then is a ruling for the enforcement, not for this gate',
},
{
kind: 'omit',
type: 'agent',
path: ROOT_PATH,
key: 'structuredOutput',
why: 'declared, not enforced yet — `[EXPERIMENTAL — not enforced]` in its own describe and `experimental` in the liveness ledger: parsed, no runtime consumer. No offer until it is enforced; whether to offer it then is a ruling for the enforcement, not for this gate',
},
{
kind: 'omit',
type: 'action',
Expand Down
Loading