This document describes the supported workflow authoring grammar for the deterministic-workflow-step design.
Higher-order workflow references are supported in a narrow, explicit form:
:type :delegate:target reuses canonical source-spec shape{:type :workflow-ref :name "builder"}It documents the author-facing :type :invoke | :session | :delegate model.
For the conceptual explanation of this design, see
doc/workflow-grammar-concepts.md.
workflow ::= workflow-map
workflow-map ::= {:steps [step+]
terminal-contract?}
terminal-contract ::= :terminal-contract {:handoff {:type :markdown-handoff-data}}
step ::= invoke-step | session-step | delegate-step
invoke-step ::= {:name step-name
:type :invoke
:operation operation-id
:args arg-map
yields?
control-flow*}
session-step ::= {:name step-name
:type :session
session-config-entry*
:contributions [contribution+]
outputs?
yields?
control-flow*}
delegate-step ::= {:name step-name
:type :delegate
:target (workflow-name | source-spec)
:prompt-string (string | template-contribution)
:context? [source-item*]
outputs?
yields?
control-flow*}
control-flow ::= :judge judge-spec
| :on outcome-map
| :max-iterations pos-int
judge-spec ::= llm-judge | invoke-judge
llm-judge ::= {:type :llm
judge-session-config-entry*
:contributions [contribution+]
outputs?}
invoke-judge ::= {:type :invoke
:operation operation-id
:args arg-map}
outcome-map ::= {outcome transition-map}+
transition-map ::= {:goto goto-target
max-iterations-clause?}
max-iterations-clause ::= :max-iterations pos-int
goto-target ::= :next | :previous | :done | step-name
contribution ::= source-contribution | template-contribution
source-contribution ::= {:type :source
:from source-ref
source-projection?}
template-contribution ::= {:type :template
:text string
:vars {var-name source-spec}*}
source-item ::= {:type :source
:from source-ref
source-projection?}
source-spec ::= {:from source-ref
source-projection?}
For delegate targets:
- `:target "builder"` is the static form
- `:target {:from ... :path [...]}` is the dynamic higher-order form
- dynamic target resolution must produce a `workflow-ref`
- free-form text and plain strings are not valid dynamic workflow-reference values
source-projection ::= :path path
| :projection projection
source-ref ::= :workflow-input
| :workflow-original
| {:step step-name :output output-key}
| {:step step-name :yield yield-field}
outputs ::= {output-key output-spec}+
output-spec ::= delegate-handoff-output
| session-text-output
| session-structured-output
| judge-structured-output
delegate-handoff-output ::= {:source :delegate/handoff}
session-text-output ::= {:source :session/final-llm-reply}
session-structured-output ::= {:source :session/structured-output
:mode :structured
schema-contract
invalid-policy?}
judge-structured-output ::= {:source :judge/structured-output
:mode :structured
schema-contract
invalid-policy?}
schema-contract ::= :schema-id keyword
:schema-version pos-int
:schema malli-schema
:json-schema json-schema-map?
:strategy-preference (:provider-native | :prompted-json)?
:fallback (:prompted-json | :none)?
:require-provider-native? boolean?
invalid-policy ::= :on-invalid {:action :fail-fast}
| :on-invalid {:action :retry
:max-attempts pos-int}
output-key ::= keyword
yield-field ::= keyword
arg-map ::= {keyword (literal | source-spec)}*
session-config-entry ::= :model model-selection-spec
| :tools [tool-id*]
| :skills [skill-id*]
| :temperature double ;; optional, range [0.0, 2.0]; absent = provider default
| session-config-extension
judge-session-config-entry ::= :model model-selection-spec
| :tools [tool-id*]
| :skills [skill-id*]
| :temperature double ;; optional, range [0.0, 2.0]; absent = provider default
| judge-session-config-extension
model-selection-spec ::= external-nonterminal-defined-in-doc-model-selection-grammar
yields ::= {:type :data :data output-keyword}
| {:type :text :text output-keyword}
| {:type :error :reason keyword :message string :details? map}
| {:type :delegated}
output-keyword ::= keyword
step-name ::= string
workflow-ref ::= {:type :workflow-ref
:name workflow-name}
workflow-name ::= string
operation-id ::= string
tool-id ::= string
skill-id ::= string
var-name ::= string
outcome ::= string | keyword
path ::= vector
projection ::= map
literal ::= string | keyword | number | boolean | nil | vector | map
malli-schema ::= vector | map | keyword
pos-int ::= integer
map ::= clojure-map
vector ::= clojure-vector
string ::= clojure-string
keyword ::= clojure-keyword
number ::= clojure-number
boolean ::= true | false
nil ::= nil
Session steps may declare machine-facing structured outputs under the existing
step-local :outputs map. LLM judges may declare judge-local structured
outputs under their own :outputs map. Delegate :outputs remain supported
for handoff data; outputs is no longer delegate-only.
Structured outputs use :source :session/structured-output for ordinary
session step output and :source :judge/structured-output for LLM judge
output. Both forms require :mode :structured plus a Malli-compatible schema
contract (:schema-id, :schema-version, and :schema). Provider-native and
prompted-JSON request shaping also require an explicit :json-schema; the
runtime does not derive JSON Schema from Malli. Authors may set
:strategy-preference :provider-native, :fallback :prompted-json or :none,
and :require-provider-native? true. Omitted strategy defaults to native-first
with prompted-JSON fallback. A session step may have at most one session
structured-output entry, and an LLM judge may have at most one judge
structured-output entry. Authors who need multiple machine-facing values should
group them as fields inside one structured map schema and address fields with
:path.
Downstream references to session structured outputs use the normal source-spec shape:
{:from {:step "classify-reproduction" :output :classification}
:path [:next-action]}
The path is resolved against the validated structured :value, never by
parsing prose. If the source output is missing, non-structured, invalid, or the
path is absent, resolution fails clearly.
Judge structured outputs are judge-local in this slice. They are available to
the judge result and transition evaluation, but are not implicitly promoted into
the parent step's {:step ... :output ...} namespace. A later step that needs
the same data must consume an explicitly declared session structured output or a
future explicit promotion/export contract, not a hidden judge-output ref.
Prompted fallback means the AI adapter injects schema-guided JSON-only
instructions into the provider request for one JSON value matching the declared
JSON Schema for the one declared structured-output key. Workflow runtime then
parses the returned text and schema-guided coercion maps JSON object keys and
enum strings into the declared Malli-domain values when the value is an object,
and also validates scalar, array, boolean, number, string, and null values when
the schema allows them. Raw text is retained even when coercion and validation
succeed. Provider-native structured output likewise requests one
schema-constrained JSON value and records it behind the single declared
structured-output key. If native support is required or fallback is :none, an
unsupported resolved model/transport fails with :unsupported-structured-output
instead of retrying as prose. Authors who need multiple named fields or
:path-addressable subvalues should use a map/object schema for that one JSON
value.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |