Docs
Reference

Orchestration Constructs

Canonical reference for non-step flow forms and metadata labels used for readable orchestration timelines.

Quick Answer

Use standard flow forms (if, for, loop, cond, case, etc.) inside :flow, and annotate control nodes with metadata labels (^{:label ...}) so runs render human-readable branch and loop names.
When you need bounded child-workflow batch spawn/collect, use the :fanout step instead of a non-step form.

Supported Non-Step Forms

  • sequencing: let, do
  • branching: if, if-not, when, when-not, cond, case
  • iteration: for, doseq, loop, recur
  • child orchestration: flow/call-flow

:fanout is intentionally not listed here because it is a step, not a non-step form.
See Step Fanout for bounded child-workflow fanout behavior.

Child Flow Calls

Use the positional flow/call-flow form:

(flow/call-flow :child-flow
                {:plain "child input"})

For installed public child flows, pass the installation in the third options map:

(flow/call-flow :reviewed-ai-response-generator
                {:prompt prompt}
                {:installation-id reviewed-ai-response-installation-id})

For an author-owned child flow in the same workspace, a draft run may need a
child profile when the child uses profile-bound connections, secrets, or setup
values. An unqualified call from a draft parent automatically selects the
child flow's enabled draft profile:

(let [input (flow/input)]
  (flow/call-flow :review-social-draft input))

The inherited draft marker is resolved separately for each child flow, so this
does not reuse or expose the parent's profile id. If the child has no enabled
draft profile, pass an explicit child :profile-id after configuring it. For an
installed public child flow, use :installation-id; a workspace draft profile
is not the consumer's installation. When a parent is already running inside a
linked public app, omit the target and let the linked install context flow to
same-app children.

Do not put :installation-id in the child input map, and do not use a one-map
shape such as (flow/call-flow {:flow-slug ... :installation-id ... :input ...}).

Label Metadata

Attach metadata directly to a control form:

  • shorthand: ^"My Label" (if ...)
  • explicit: ^{:label "My Label"} (if ...)

Branch-specific labels on if / if-not:

^{:label "Risk gate"
  :yes "Requires approval"
  :no "Auto-approve"}
(if (:needs-approval summary)
  (flow/step :wait :approval {...})
  {:action :approve})

Loop metadata:

^{:label "Retry status poll" :max-iterations 6}
(loop [attempt 0]
  (let [status (flow/step :http :get-status
                 {:connection :orders-api
                  :method :get
                  :path "/orders/status"})]
    (if (or (= :done (:state status))
            (>= attempt 5))
      status
      (recur (inc attempt)))))

for / doseq label example:

^{:label "Enrich each order"}
(for [order orders]
  (flow/step :http :enrich-order
    {:connection :orders-api
     :method :get
     :path (str "/orders/" (:id order))}))

Branching Guidance

  • use if / if-not for binary business gates and set :yes / :no
  • use when / when-not for one-sided conditionals
  • use cond / case for multi-branch routing; add ^{:label ...} on the whole form
  • keep transformation logic in top-level :functions, not inside branch bodies

Design Rules

  • label major decisions and loops; skip labels only for trivial one-liners
  • keep labels business-facing ("Approve order?", "Retry payment status")
  • keep :flow focused on orchestration, and call steps/functions/templates for heavy logic/data
  • combine labels with :persist and templates to keep large flows readable and size-safe
  • use flow/poll for bounded external status polling instead of custom unbounded loops

Related

As of Jul 20, 2026