Reference

Flow Definition

Canonical Breyta flow definition reference for required fields and high-value semantics.

Quick Answer

A Breyta flow is an EDN map with identity, concurrency, triggers, deterministic :flow, and optional requirements/templates/functions.

Fields At A Glance

FieldRequiredPurposeNotes
:slugYesStable flow identifierUsed by CLI/API commands.
:nameYesHuman-readable display nameSafe to change without changing slug identity.
:concurrencyYesOverlap/version execution policyDefines overlap rules and version behavior.
:triggersYesRun entry pointsSupports manual (:manual), schedule (:schedule + cron), and webhook/event (:event + :config {:source :webhook ...}).
:flowYesDeterministic orchestration bodyKeep orchestration-focused; move shaping/transforms to top-level :functions.
:descriptionNoAdditional context for operators/usersOptional metadata only.
:deploy-key-requiredNoProtect release/promotion operations behind a shared deploy keyStored as flow metadata; when true, guarded release/promotion operations require a valid key.
:requiresNoRuntime dependencies and setup/run form inputsDeclares connection/secret/form requirements resolved via bindings/profile inputs. Use :provided-by :author for requirements satisfied by the flow author instead of the installation user. For :kind :form, use :collect :setup (default) for setup entered once and reused on later runs, or :collect :run for input required on each run.
:templatesNoReusable large static contentBest for request bodies, prompts, queries, notifications.
:functionsNoReusable Clojure transformsCall via flow/step :function refs from :flow.
:tagsNoClassification/feature tagsOptional metadata only; does not gate install/run behavior.

Manual Trigger Labels

For manual triggers, :label is also reused as the manual run CTA across Resource UI surfaces.

That includes:

  • the flow page run action
  • installation and launch page primary run buttons
  • the setup side panel run button

If multiple manual triggers are declared, Resource UI currently uses the first manual trigger label.
If no manual trigger label is set, the fallback copy is Run now.

Example:

{:triggers [{:type :manual
             :label "Ask project question"
             :enabled true
             :config {:fields [{:name :question
                                :type :text
                                :label "Question"
                                :required true}]}}]}

Deploy-Key Guard

Use :deploy-key-required to gate release operations for a flow:

{:slug :critical-flow
 :name "Critical Flow"
 :deploy-key-required true
 :concurrency {:type :singleton :on-new-version :supersede}
 :flow '(let [input (flow/input)] input)}

Behavior:

  • :deploy-key-required true requires a valid deploy key for guarded release operations (for example flows release and flows promote).
  • Disabling the guard (:deploy-key-required false) also requires a valid deploy key.
  • Enabling the guard fails unless BREYTA_FLOW_DEPLOY_KEY is configured for the Breyta environment.
  • The deploy key is not modeled as :requires, bindings, or a connection. It is a server-side release secret.

Draft Vs Live (Definition Semantics)

The flow definition fields are the same, but runtime resolves them from different targets:

Aspectdraft targetlive target
Definition sourceLatest pushed working copy in workspace.Installed released version.
How target changesbreyta flows push --file ...breyta flows release <slug> or breyta flows promote <slug>
:requires resolutionbreyta flows configure <slug> ... values.breyta flows configure <slug> --target live ... or installation-specific configuration values.
Webhook trigger endpoint surfaceDevelopment/staging-oriented trigger endpoints for workspace iteration.Installed target endpoints for consumer/runtime traffic; can differ from draft endpoints.
Typical verification runbreyta flows run <slug> --waitbreyta flows run <slug> --target live --wait
Best useAuthoring iteration and fast feedback.Stable consumer/runtime behavior.

flows push does not modify live; it only updates draft.

Form Requirements

Use {:kind :form ...} inside :requires when the flow needs structured user input.

Top-level form requirement keys:

KeyRequiredMeaning
:kindYesMust be :form.
:fieldsYesOne field map or a vector of field maps.
:labelNoGroup label shown in setup/run UI.
:collectNo:setup (default) means fill once and reuse; :run means ask every run.
:provided-byNo:installer (default) or :author. Author-provided requirements are not shown to the installer.

Field keys:

KeyRequiredMeaning
:key or :nameYesStable submitted field id.
:labelNoDisplay label in UI.
:field-typeNoInput type. Defaults to :text.
:requiredNoWhen true, the field must be filled before save/run.
:placeholderNoHelper text shown in the input UI.
:defaultNoDefault value shown when the form first loads.
:optionsFor :selectAllowed values.
:multipleFor :resourceAllow selecting more than one resource.
:resource-typesFor :resourceOptional resource type filter such as [:file :result].
:acceptFor :resourceOptional MIME filter such as ["application/pdf" "text/plain" "text/*"].
:slotFor :resourceOptional local blob-storage slot such as :archive.
:storage-backendFor :resourceOptional explicit storage backend filter such as :gcs. Usually inferred from :slot when present.
:storage-rootFor :resourceOptional explicit storage root such as "reports/acme". Usually inferred from :slot when present.
:path-prefixFor :resourceOptional relative path prefix under the effective storage root, such as "exports/2026".
:sourceFor :resourceLegacy picker routing override. Omit in normal flow authoring.

Supported :field-type values:

:field-typeUI behaviorNotes
:textSingle-line text inputDefault when omitted.
:textareaMulti-line text boxGood for prompts and instructions.
:selectDropdownRequires :options.
:booleanCheckbox/toggleRequired means the value must be true.
:numberNumeric inputSubmitted as text, validated/coerced by the flow contract.
:dateDate pickerBrowser-native date input.
:timeTime pickerBrowser-native time input.
:datetimeDate-time pickerBrowser-native datetime-local input.
:resourceSearch/select workspace resourcesRun-only for now; use with :collect :run.

Resource field expectations:

  • :field-type :resource is currently supported only with :collect :run.
  • The run UI renders a resource picker only when a resource field is declared.
  • Selected values are passed as resource references, not full file contents.
  • Use :multiple true when the flow expects a collection of resources.
  • :accept matches resource MIME types. Use exact values like "application/pdf" or wildcard prefixes like "text/*".
  • :resource-types and :accept are combined, so a resource must satisfy both filters when both are present.
  • Resources without matching MIME metadata are excluded when :accept is set.
  • Use :slot <slot> to scope the picker to a declared local blob-storage slot.
  • When :slot points at an installer-owned blob-storage slot, the picker reuses that slot's configured storage root automatically.
  • Use :path-prefix when the picker should narrow to a subfolder under the effective storage root.
  • Picker storage filters use normalized index fields: storage_backend, storage_root, and path_under_root.
  • :storage-backend and :storage-root are optional explicit filters for unbound resource pickers. Slot-bound pickers infer them from the resolved slot binding.
  • Installer-owned :blob-storage slots automatically add required setup controls for the connection binding and storage root; picker scoping reuses that configured root automatically.
  • For end-user installations, the effective default root is private by default: installations/<profile-id>/<prefix>, derived from the authored prefix until an explicit root is saved.
  • For platform-backed persists, that configured root becomes the exact write base: workspaces/<ws>/storage/<root>/... with no extra hidden flow or step segments.
  • :source is still accepted as a legacy/internal override, but normal authored flows should not need it.

Practical installer model:

  • local installer-owned slot such as :archive: the author declares a semantic prefix such as "reports", and an end-user installation derives a private effective root such as installations/<profile-id>/reports until an explicit root is saved
  • another flow shares only when its own local slot is explicitly configured to the same concrete storage location
  • :prefers on a blob-storage slot records intended upstream sharing, but it does not auto-select or persist the consumer root
  • top-level :connections remains display metadata for icons and labels; blob-storage authoring lives in :requires

That means the authoring surface stays local:

{:requires [{:slot :archive
             :type :blob-storage
             :label "Archive storage"
             :config {:prefix {:default "reports"}}}]}

and the installation resolves that local name concretely:

An end-user installation with profile prof-1 therefore starts with an effective root of:

installations/prof-1/reports

If the installer later chooses an explicit shared root, that saved binding wins:

{:bindings {:archive {:binding-type :connection
                      :connection-id "platform"
                      :config {:root "reports/customer-a"}}}}

Optional setup hint for cross-flow sharing:

{:requires [{:slot :archive
             :type :blob-storage
             :label "Archive storage"
             :prefers [{:flow :daily-report-export
                        :slot :archive}]
             :config {:prefix {:default "reports"}}}]}

:prefers does not rewrite the scoped default root. To share, the installer still must explicitly save the same connection + root on both installations.

How form fields appear to the flow:

  • Setup-collected fields and run-collected fields both appear in flow/input.
  • The flow reads them by their declared :key/:name.
  • Resource fields arrive as one resource ref or a vector of resource refs, depending on :multiple.

Examples:

{:requires [{:kind :form
             :collect :setup
             :label "Setup"
             :fields [{:key :region
                       :label "Region"
                       :field-type :select
                       :required true
                       :options ["EU" "US"]}]}
            {:slot :archive
             :type :blob-storage
             :label "Archive storage"
             :config {:prefix {:default "reports"
                               :label "Folder prefix"
                               :placeholder "reports/customer-a"}}}]}

{:connections [{:icon-url "/assets/connections/storage.svg"
                :label "Storage"}]}

{:requires [{:kind :form
             :collect :run
             :label "Run input"
             :fields [{:key :question
                       :label "Question"
                       :field-type :text
                       :required true}
                      {:key :resources
                       :label "Resources"
                       :field-type :resource
                       :slot :archive
                       :required true
                       :multiple true
                       :accept ["application/pdf" "text/plain"]}]}]}

{:requires [{:slot :archive
             :type :blob-storage
             :label "Archive storage"
             :config {:prefix {:default "reports"}}}
            {:kind :form
             :collect :run
             :label "Cross-flow run input"
             :fields [{:key :report
                       :label "Archived report"
                       :field-type :resource
                       :required true
                       :accept ["application/pdf"]
                       :slot :archive}]}]}

{:requires [{:kind :form
             :collect :run
             :label "Run input"
             :fields [{:key :resources
                       :label "Text resources"
                       :field-type :resource
                       :required true
                       :multiple true
                       :accept ["text/*"
                                "application/json"
                                "application/xml"
                                "application/edn"]}]}]}

{:flow
 '(let [input (flow/input)]
    {:region (:region input)
     :question (:question input)
     :resources (:resources input)})}

Example runtime input:

{:region "EU"
 :question "What changed?"
 :resources [{:type :resource-ref
              :uri "res://v1/ws/ws-1/file/demo-a"}
             {:type :resource-ref
              :uri "res://v1/ws/ws-1/result/demo-b"}]}

Notes:

  • :resource-types is optional. Omit it for the default file picker.
  • Persisted blob artifacts and uploads are :file resources.
  • Use :resource-types [:result] only when the flow should browse structured run outputs or captured results instead of files.
  • For slot-bound file pickers, use :path-prefix to narrow inside the installer-chosen root without hardcoding that root in flow source.

Supported Orchestration Constructs

Inside :flow, supported non-step forms are:

CategoryConstructsTypical Use
Binding / sequencinglet, doBuild deterministic orchestration pipelines.
Branchingif, if-not, when, when-not, cond, caseRoute execution by state or decision flags.
Iterationfor, doseq, loop, recurDeterministic fan/loop orchestration.
Child flow orchestrationflow/call-flowDelegate larger branches to child flows.
Polling helperflow/pollBounded polling built on deterministic loop/sleep semantics.

Use metadata labels to make branches and loops readable in run timelines:

  • ^"Review Gate" shorthand label
  • ^{:label "Review Gate"} explicit label
  • ^{:label "Review Gate" :yes "Approved" :no "Rejected"} for if/if-not
  • ^{:label "Retry Loop" :max-iterations 5} for loop

Common Orchestration Shape

Use one orchestration layer in :flow, then delegate:

  • shaping/normalization to :functions
  • large static bodies/prompts/queries to :templates
  • heavy outputs to :persist
  • larger branches to child flows via flow/call-flow
{:functions [{:id :prepare
              :language :clojure
              :code "(fn [input] {:order-id (:order-id input) :lookup-key (str \"orders:\" (:order-id input))})"}]
 :flow
 '(let [input (flow/input)
        prepared (flow/step :function :prepare
                   {:ref :prepare :input input})
        order (flow/step :http :fetch-order
                {:connection :orders-api
                 :method :get
                 :path (str "/orders/" (:order-id prepared))
                 :persist {:type :blob}})
        route (flow/call-flow :order-routing
                {:order-ref (:ref order)
                 :lookup-key (:lookup-key prepared)})
        result ^{:label "Apply routed action?"
                 :yes "Call apply flow"
                 :no "Skip apply"}
               (if (:should-apply route)
                 (flow/call-flow :order-apply route)
                 {:status :skipped})]
    {:order-ref (:ref order)
     :route route
     :result result})}

:persist Path Modes

Use the two blob persist modes like this:

  • :persist {:type :blob ...} without :slot writes under the runtime-managed base workspaces/<ws>/persist/<flow>/<step>/<uuid>/...
  • :persist {:type :blob :slot :archive ...} writes under the installer-bound storage base workspaces/<ws>/storage/<root>/...
  • In both modes, :persist :path stays relative to that base and :filename stays the leaf name
  • Use :slot only when installers should control the storage root or when runtime resource pickers should reuse the same storage scope

Example:

{:flow
 '(let [report-a (flow/step :http :download-inline-managed
                   {:connection :reports-api
                    :response-as :bytes
                    :persist {:type :blob
                              :path "exports/{{input.customer-id}}"
                              :filename "summary.pdf"}})
        report-b (flow/step :http :download-slot-managed
                   {:connection :reports-api
                    :response-as :bytes
                    :persist {:type :blob
                              :slot :archive
                              :path "exports/{{input.customer-id}}"
                              :filename "summary.pdf"}})]
    {:runtime-managed (:ref report-a)
     :slot-managed (:ref report-b)})}

With input.customer-id = "cust-77":

  • runtime-managed path: workspaces/<ws>/persist/<flow>/<step>/<uuid>/exports/cust-77/summary.pdf
  • slot-managed path with root reports/acme: workspaces/<ws>/storage/reports/acme/exports/cust-77/summary.pdf

Determinism Rules

  • keep orchestration deterministic in :flow
  • put side effects in flow/step boundaries
  • avoid non-deterministic random/time calls in workflow body logic

Limit-Aware Authoring

  • payload limit is 150 KB for the core flow definition map (excluding template/function content)
  • effective total package can be ~2.1 MB with template/function totals
  • step output and webhook payload limits are enforced at runtime
  • use templates for large static content and treat :persist as a common default for variable-size step outputs

See limits: Limits And Recovery

Related

As of Mar 27, 2026