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-configvalidates action config, worker-action input contracts, output contracts, and input mappings.workflow/sequence-input-schemavalidates sequence-start JSON Schema contracts.workflow/matrix-bundlevalidates importable matrix bundles.
Download the minimal matrix bundle.
Mental Model
Section titled “Mental Model”Amon Sul workflow JSON has three layers:
- Actions are executable units. An action is one
CALL,CHECK,CODE, orODATAoperation. - Sequences are ordered chains of actions. They define how caller inputs and previous outputs flow into each action.
- Matrices are policy. They attach sequences to statuses and decide the next status from exit conditions.
Sequences produce facts. Matrices interpret those facts.
Action Types
Section titled “Action Types”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, orPATCH.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_conditionseveritymessage
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_codepath(relative service path; metadata IDs stay in local editor state)entity_setoperationtarget_typekey_fieldsselectexpandfilterbodysuccessCriteria
See the ODATA example.
Worker-Action Input And Output Contracts
Section titled “Worker-Action Input And Output Contracts”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:
stringnumberbooleanobjectarray
Output contracts describe the named output variable an action contributes:
{ "variable_name": "submission", "description": "External submission response.", "fields": { "submissionId": "string", "decision": "string", "message": "string" }}Sequence Steps
Section titled “Sequence Steps”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.
Matrix Routing
Section titled “Matrix Routing”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_conditionthat matches an outcome rule, the matrix uses that rule’sstatus_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_idtonullanddefault_outcome_preserves_statustotrue. - 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.
Bundle Shape
Section titled “Bundle Shape”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.
Validation and import
Section titled “Validation and import”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.
Composite Bundles
Section titled “Composite Bundles”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_coderesolves to that matrix’s latest published version at import time, so publish the referenced matrices first.milestones[].status_codesare 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 amessageexplaining the block to the caller.entity_typeslists the entity types the composite governs. Pass--entity-types=a,bon 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.
Builder Rules
Section titled “Builder Rules”- 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_conditionfacts and let the matrix route. - Use stable uppercase codes for statuses, actions, and sequences.
- Use
$keyreferences inside JSON bundles instead of database ids. - Keep example bundles valid against
@amon-sul/amon-contracts.