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
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
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
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, orchangeUnitstyped 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 →