Example: JSON Schema

A clarification step that emits a structured handoff package matching a JSON Schema. Downstream jobs read individual fields off the validated payload.


Spec

json
1{
2  "ref": "handoff-package",
3  "kind": "json-schema",
4  "outputContract": {
5    "title": "Handoff Package",
6    "mode": "workspace-files",
7    "targetSlot": { "kind": "deliverables" },
8    "files": [
9      {
10        "canonicalPath": "handoff.json",
11        "required": true,
12        "purpose": "Structured handoff with change units, problem landscape, and constraints.",
13        "contentKind": "json"
14      }
15    ]
16  },
17  "contentSchema": {
18    "type": "object",
19    "required": ["changeUnits", "problemLandscape"],
20    "properties": {
21      "changeUnits": {
22        "type": "array",
23        "items": {
24          "type": "object",
25          "required": ["id", "title"],
26          "properties": {
27            "id": { "type": "string" },
28            "title": { "type": "string" }
29          }
30        }
31      },
32      "problemLandscape": { "type": "string" }
33    }
34  }
35}

Workflow

yaml
1apiVersion: xema.dev/workflow/v1alpha1
2kind: Workflow
3metadata:
4  name: json-schema-example
5  version: 1.0.0
6on:
7  workflow_dispatch:
8    inputs:
9      request:
10        type: string
11        required: true
12permissions:
13  artifacts: write
14jobs:
15  clarify:
16    title: Clarify request and emit handoff package
17    uses: xema/agent
18    with:
19      agentRef: clarification-coordinator
20      stageKey: clarification
21      deliverableSpecRef: handoff-package
22      agentSession: true
23      agentContext:
24        prompt: ${{ inputs.request }}
25    outputs:
26      changeUnits: ${{ job.outputs.deliverable.content.value.changeUnits }}
27      handoffPackage: ${{ job.outputs.deliverable.content.value }}
28      problemLandscape: ${{ job.outputs.deliverable.content.value.problemLandscape }}
29
30  per-cu:
31    needs: [clarify]
32    strategy:
33      dynamic:
34        from: ${{ needs.clarify.outputs.changeUnits }}
35        as: changeUnit
36        keyBy: id
37        maxEntries: 32
38    uses: xema/agent
39    with:
40      agentRef: requirements
41      stageKey: requirements
42      changeUnitId: ${{ matrix.changeUnit.id }}
43      deliverableSpecRef: requirements-standard
44      agentSession: false
45      agentContext:
46        changeUnit: ${{ matrix.changeUnit }}
47        handoffPackage: ${{ needs.clarify.outputs.handoffPackage }}

Reading the result

ts
1// Producing job (clarify)
2agentResult: {
3  deliverable: {
4    specRef: 'handoff-package',
5    kind: 'json-schema',
6    targetSlot: { kind: 'deliverables' },
7    content: {
8      kind: 'json',
9      artifactId: 'art-1',
10      versionId: 'ver-1',
11      version: 1,
12      value: {
13        changeUnits: [
14          { id: 'cu-1', title: 'Auth migration' },
15          { id: 'cu-2', title: 'Billing rework' },
16        ],
17        problemLandscape: 'Legacy SSO blocks SaaS rollout...',
18      },
19    },
20  },
21}

deliverable.content.value IS the validated JSON payload. Sub-fields (value.changeUnits, value.problemLandscape) are available directly to downstream expressions.

Where the schema is enforced

The contentSchema is enforced at harvest time, as discovery. The harvester walks the paths the spec's output contract declares, parses each as JSON, and asks deliverable-specs-api which candidate matches — Ajv (Draft 2020-12) compiles the schema and validates the payload. A file that does not match is rejected as a candidate with a recorded reason:

  • Missing changeUnits, or changeUnits typed as a string → SCHEMA_MISMATCH, with the JSON Pointer.
  • The file parsed but is not a JSON object → NOT_OBJECT.
  • Markdown where JSON was declared → the candidate does not parse and is skipped.

When no candidate matches, the harvester returns no structured value and records a warning naming every path it tried. It does not throw; clarify.outputs.changeUnits is then simply absent for downstream jobs.

To turn that into a gate the workflow can branch on, add an xema/validate-deliverables job downstream. See 04 Validation.


Previous: ← Document Template Next: Code into a repo →