Skip to content

Workflow JSON

This guide is for builders, integrators, and LLM agents that need to author Amon Sul workflow matrices as JSON.

The authoritative runtime contracts live in @amon-sul/amon-contracts:

  • workflow/action-config validates action config, worker-action input contracts, output contracts, and input mappings.
  • workflow/sequence-input-schema validates sequence-start JSON Schema contracts.
  • workflow/matrix-bundle validates importable matrix bundles.

Download the minimal matrix bundle.

Amon Sul workflow JSON has three layers:

  1. Actions are executable units. An action is one CALL, CHECK, CODE, or ODATA operation.
  2. Sequences are ordered chains of actions. They define how caller inputs and previous outputs flow into each action.
  3. Matrices are policy. They attach sequences to statuses and decide the next status from exit conditions.

Sequences produce facts. Matrices interpret those facts.

Every action record has the same outer shape:

{
"$key": "submit_claim",
"matrix_id": "claim_matrix",
"code": "SUBMIT_CLAIM",
"name": "Submit claim",
"type": "CALL",
"description": "Submits the claim to the external warranty system.",
"config": {},
"input_contract": {},
"output_contract": {}
}

Use $key values as local references inside the bundle. Import resolves these keys to real database ids.

Use CALL for HTTP calls through the integration layer.

Canonical keys:

  • system_code: exact connection code resolved in the execution lane.
  • path: relative API path or path template.
  • method: GET, POST, PUT, DELETE, or PATCH.
  • headers, pathParams, queryParams, body: optional request data.
  • responseMapping: maps output names to response paths.
  • successCriteria: HTTP and business success checks.
  • errorMapping: response paths for error message/code.

Use system_code and a relative path. Both are required to execute or Test; incomplete drafts may omit them. Full and authority URLs are rejected, including after template substitution. Do not use system_id, external_system_id, external_system_code, or url.

See the CALL example.

Use CHECK for business-rule validation.

The rules array is JSON rules engine compatible. Rule events should put workflow routing facts in event.params, especially:

  • exit_condition
  • severity
  • message

See the CHECK example.

Use CODE for small custom transformations or calculations.

The config stores JavaScript in code and optional static parameters. CODE runs from saved configuration in the local QuickJS sandbox; it does not require a deployed action worker.

See the CODE example.

Use ODATA for SAP/OData operations.

Common keys:

  • system_code
  • path (relative service path; metadata IDs stay in local editor state)
  • entity_set
  • operation
  • target_type
  • key_fields
  • select
  • expand
  • filter
  • body
  • successCriteria

See the ODATA example.

Worker-action input contracts remain field maps describing the data each action receives:

{
"claimId": {
"required": true,
"type": "string",
"description": "Claim id supplied by the caller."
}
}

Supported worker-action field type values are:

  • string
  • number
  • boolean
  • object
  • array

Output contracts describe the named output variable an action contributes:

{
"variable_name": "submission",
"description": "External submission response.",
"fields": {
"submissionId": "string",
"decision": "string",
"message": "string"
}
}

A sequence defines its callable inputs:

{
"$key": "submit_sequence",
"matrix_id": "claim_matrix",
"code": "SUBMIT_CLAIM_SEQUENCE",
"name": "Submit claim sequence",
"input_contract": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"claimId": {
"type": "string",
"description": "Claim id supplied by the caller."
}
},
"required": ["claimId"],
"additionalProperties": false
}
}

Sequence-start contracts use this constrained JSON Schema Draft 2020-12 shape even when there are no inputs. The canonical empty contract keeps the same five root keys with empty properties and required. A supplied {} or worker-action field map is invalid.

The ordered action list lives in action_sequence_actions:

{
"$key": "step_submit",
"sequence_id": "submit_sequence",
"action_id": "submit_claim",
"position": 3,
"input_mapping": {
"claimId": "inputs.claimId",
"normalizedComment": "outputs.normalization.normalizedComment"
}
}

Use position for deterministic order. Keep input_mapping explicit so an LLM or human can understand the data flow without reading code.

Statuses belong to one matrix:

{
"$key": "draft",
"matrix_id": "claim_matrix",
"code": "DRAFT",
"name": "Draft",
"is_initial": true
}

action_matrix_sequences attaches a sequence to a status:

{
"$key": "draft_submit",
"action_matrix_id": "claim_matrix",
"status_id": "draft",
"sequence_id": "submit_sequence",
"default_outcome_status_id": "submitted",
"default_outcome_preserves_status": false,
"default_technical_failure_status_id": "technical_failure",
"outcome_rules": [
{
"exit_condition": "rejected",
"status_id": "rejected",
"severity": "error"
}
]
}

Routing rules:

  • If an action produces an exit_condition that matches an outcome rule, the matrix uses that rule’s status_id.
  • If the sequence succeeds without a matching rule, the matrix uses default_outcome_status_id.
  • To keep the current status after an unmatched successful outcome, set default_outcome_status_id to null and default_outcome_preserves_status to true.
  • If the sequence fails technically, the matrix uses default_technical_failure_status_id.

Optional position orders the sequences offered at one status (lowest first, then sequence code); Amon never interprets it. Without it, rows keep their bundle order at that status.

Every mapping must configure exactly one default mode: a non-null default_outcome_status_id, or default_outcome_preserves_status: true with a null target. Older bundles that omit the boolean are treated as default_outcome_preserves_status: false. Preservation never changes explicit exit-rule or technical-failure routing.

An importable bundle has these top-level arrays:

{
"meta": { "format_version": 1 },
"entity_types": [],
"action_matrixes": [],
"action_statuses": [],
"action_actions": [],
"action_sequences": [],
"action_sequence_actions": [],
"action_matrix_sequences": [],
"action_entity_matrix": []
}

entity_types and action_entity_matrix are optional, but include them when the matrix should be available to an entity type such as claim or vehicle.

Review JSON against the workflow contracts and have your definition administrator validate it before import. Import and publication change tenant definitions; review the tenant, references and lane binding before activation.

A composite binds one or more published matrices together as roles, tracks role-scoped milestones, and lets one role’s sequence gate on another role’s milestone. This is how multi-track policies (a warranty claim’s dealer and vendor tracks, a vehicle’s logistics and retail tracks) coordinate.

{
"meta": { "format_version": 1 },
"code": "warranty_claim",
"name": "Warranty claim",
"description": "Warranty claim with a dealer track and a vendor recovery track.",
"roles": [
{ "role_slug": "dealer", "role_name": "Dealer", "matrix_code": "warranty_dealer" },
{ "role_slug": "vendor", "role_name": "Vendor", "matrix_code": "warranty_vendor" }
],
"milestones": [
{
"role_slug": "dealer",
"code": "DEALER_APPROVED",
"name": "Dealer claim approved",
"status_codes": ["APPROVED", "CREDITED"]
}
],
"guards": [
{
"consumer_role_slug": "vendor",
"consumer_sequence_code": "raise_recovery",
"required_role_slug": "dealer",
"required_milestone_code": "DEALER_APPROVED",
"message": "Dealer claim must be approved before recovery can be raised (DEALER_APPROVED)"
}
],
"entity_types": []
}
  • roles[].matrix_code resolves to that matrix’s latest published version at import time, so publish the referenced matrices first.
  • milestones[].status_codes are the statuses, in the role’s matrix, that satisfy the milestone.
  • guards[] block a role’s sequence (consumer_role_slug + consumer_sequence_code) until another role’s milestone (required_role_slug + required_milestone_code) is reached, and carry a message explaining the block to the caller.
  • entity_types lists the entity types the composite governs. Pass --entity-types=a,b on import to replace the bundle’s list for a tenant.

Re-importing the same code creates a new composite version and moves each bound entity type’s binding onto it. A binding already pointing at a different composite code is a conflict. An unpublished draft for the code blocks the import until it is published or deleted.

Outcome rules on action_matrix_sequences may omit status_id: that records the matched exit with its severity and lets the sequence continue. Only a rule with a status_id is a routed exit, and it stops the sequence at that action.

  • Prefer one clear action per external call, check, or transform.
  • Keep sequence input contracts small and explicit.
  • Do not hide routing policy inside code actions. Emit exit_condition facts and let the matrix route.
  • Use stable uppercase codes for statuses, actions, and sequences.
  • Use $key references inside JSON bundles instead of database ids.
  • Keep example bundles valid against @amon-sul/amon-contracts.