Flow Features And Config Toggles
Breyta feature matrix for authoring and operating advanced flows.
Goal
Provide one checklist of core flow fields, step capabilities, and operational toggles.
Quick Answer
Use this page to verify that your flow covers invocations, interfaces, schedules, templates, waits, notifications, persistence, table resources, and bindings with canonical config shapes.
For the first draft proof, inline small one-off bodies when that is fastest. As
complexity grows, keep :flow orchestration-focused and move repeated or bulky
content into :functions, :templates, or packaged :steps.
Treat :persist as a common default for data-heavy steps, not a niche option.
Flow Definition Surface
| Field | Purpose | Common options |
|---|---|---|
:slug, :name, :description | Identify and describe the flow. | Stable slug + editable human label. |
:concurrency | Control overlap/version handoff. | :singleton, :keyed, :on-new-version. |
:invocations | Define per-run input contracts. | Usually {:default {:inputs [...]}}. |
:interfaces | Define manual/external ingress surfaces over invocations. | :manual, :http, :webhook, :mcp, optional :enabled false. |
:schedules | Define time-based automation over invocations. | Cron, timezone, enabled state. |
:requires | Declare connection/secret/form requirements. | Configured through flows configure (or install-specific paths). |
:templates | Store reusable HTTP/LLM/DB/notify payloads. | Referenced via :template + :data. |
:functions | Store reusable Clojure transforms. | Called via flow/step :function and :ref. |
:flow | Orchestrate deterministic control logic and step calls. | Keep orchestration-focused, not transform-heavy. |
For detailed overlap/version behavior choices, see Run Concurrency.
Interface And Schedule Choice By Target
| Target | Practical surface preference | Notes |
|---|---|---|
draft | Prefer :interfaces :manual while authoring. | Generated author interface endpoints are source scoped with draft in the path. |
live | Prefer :schedules for unattended automation, plus installation/live webhook interfaces when needed. | Treat live runtime surfaces as stable entrypoints. |
Step Capabilities And Toggles
| Step / toggle | Core shape | Use |
|---|---|---|
:http | :connection, :method, :path, :template, :data, :retry | External API calls with optional retries and templating. |
:llm / :db | Template-backed prompt/query composition | Keep prompt/query bodies reusable and reviewable. |
:wait | :key, :timeout, :on-timeout, :notify, optional :webhook | Human/webhook callback orchestration with bounded waits. |
:notify | :channels (:http), optional :template | Outbound notifications and delivery hooks. |
:persist | {:type :blob} or {:type :kv} or {:type :table} | Store large outputs/state handoff as refs, files, or table resources. |
:kv | :operation (:get, :set, :append, etc.) | Durable keyed state between steps/runs. |
:table | :op (:query, :get-row, :aggregate, :schema, :export, :update-cell, :update-cell-format, :set-column, :recompute, :materialize-join) | Bounded runtime surface for persisted table resources, including authored enum/derived/reference columns and flow-native materialized joins. |
:fanout | :items, optional :max-concurrency, :on-error, :transform | Bounded batch orchestration. Async mode is limited to :call-flow child workflow items only. |
Orchestration Primitives (Non-Step)
| Primitive | Purpose | Notes |
|---|---|---|
flow/call-flow | Explicit child-flow orchestration | Reuse focused subflows instead of copying complex branches. |
Flow forms (let, do, if, cond, for, loop, etc.) | Deterministic control flow | Keep transforms in top-level :functions. |
flow/poll | Bounded status polling | Built on deterministic loop + sleep semantics. |
Metadata labels (^{:label ...}) | Timeline readability | if/if-not support :yes/:no; loop supports :max-iterations. |
flow/input | Run input entry | Call once near top and pass narrowed maps downstream. |
Use flow/call-flow when one branch grows into a separate lifecycle (for example: ingress flow -> routing flow -> apply flow). Keep parent flow responsible for orchestration and move shaping into top-level :functions.
'(let [input (flow/input)
normalized (flow/step :function :normalize {:ref :normalize :input input})
routed (flow/call-flow :billing-route (:route-input normalized))
result ^{:label "Apply routed action?"
:yes "Call apply flow"
:no "Skip apply"}
(if (:should-apply routed)
(flow/call-flow :billing-apply (:apply-input routed))
{:skipped true})]
{:routed routed
:result result})
See construct metadata and polling details:
Use :fanout when the parent needs a bounded batch of sibling child workflows that can start now and collect later.
That async path is intentionally narrow:
- all items must be
{:type :call-flow ...} - effective concurrency must be at most
5; validation rejects larger values - nested async child fanout is rejected beyond one level
- legacy non-child fanout items still work only on the sequential compatibility path
Example:
'(let [input (flow/input)
fanout (flow/step :fanout :spawn-children
{:items (:items input)
:max-concurrency 5
:on-error :collect})]
{:fanout fanout})
Loop pattern for bounded item processing with explicit persistence:
'(let [input (flow/input)
items (:items input)
batch (reduce (fn [acc item]
(let [item-result (flow/step :http :process-item
{:connection :api
:method :post
:path "/process"
:json {:id (:id item)}
:persist {:type :blob}})]
(conj acc item-result)))
[]
items)
summary (flow/step :function :summarize
{:ref :summarize
:input batch})]
summary)
Table-resource pattern for create-on-write persistence plus later bounded queries:
'(let [orders (flow/step :http :fetch-orders
{:url "https://example.com/orders"
:persist {:type :table
:table "orders"
:rows-path [:body :items]
:write-mode :upsert
:key-fields [:order-id]
:columns [{:column :customer-id
:semantic-type :reference
:reference {:table "customers"
:remote-field :customer-id}}
{:column :customer-name
:semantic-type :text
:computed {:type :lookup
:reference-column :customer-id
:field :name}}]}})
open-orders (flow/step :table :query-open-orders
{:op :query
:table orders
:where [[:status := "open"]]
:sort [[:order-id :asc]]
:page {:mode :offset
:limit 25}})
define-summary (flow/step :table :define-summary
{:op :set-column
:table orders
:column :order-summary
:definition {:semantic-type :text
:computed {:type :expr
:expr {:op :concat
:args [{:field :customer-name}
" / "
{:field :status}]}}}})]
{:orders orders
:open-orders open-orders
:define-summary define-summary})
For bounded tables, :set-column automatically backfills existing rows. :recompute remains available when you want to rerun derived/reference values later.
Dynamic enum columns are also part of the same authored-column surface:
'(flow/step :table :define-status-enum
{:op :set-column
:table orders
:column :status
:definition {:display-name "Status"
:enum {:options [{:id "open"
:name "Open"
:aliases ["OPEN" "Open"]}
{:id "in-progress"
:name "In progress"
:aliases ["IN_PROGRESS" "In Progress"]}]}}})
Enum behavior:
:enumimpliestype-hint "enum"- writes,
:update-cell, CSV import, and:recomputenormalize incoming scalar values to stable ids - matching accepts existing ids, names, and aliases
- unknown values dynamically grow the enum definition with a normalized id and a derived display name
- stored rows and query/export surfaces keep the normalized ids, while the web preview renders names
Display formatting remains render-only:
- column
:formatmetadata and sparse:update-cell-formatoverrides can renderrelative-time,date,timestamp/date-time, andcurrency - the web preview and
Copy Markdownapply those formats to the currently visible page - query/export surfaces keep canonical raw values
Use :materialize-join when the right product shape is a new derived table rather than more derived columns on an existing table:
'(let [rows (:rows (flow/input))
joined (flow/step :table :materialize-orders
{:op :materialize-join
:left {:rows rows}
:right {:table "customers"
:select [:customer-id :name]}
:join-type :left
:on [{:left-field :customer-id
:right-field :customer-id}]
:project {:keep-left :all
:right-fields [{:field :name :as :customer-name}]}
:into {:table "orders-enriched"
:write-mode :upsert
:key-fields [:order-id]}})
joined-preview (flow/step :table :query-joined
{:op :query
:table joined
:page {:mode :offset
:limit 25}})]
{:joined joined
:joined-preview joined-preview})
Current :materialize-join boundary:
- flow/runtime-backed contract with matching CLI/API entrypoints
- equality joins only
:leftand:inneronly- same-workspace table sources only
- right-side cardinality
0..1 - destination table materialization only
- no resource-panel/manual UI replay surface
Current :materialize-join semantics:
- incremental materialization only via
:appendor:upsert - no snapshot/replace mode yet
- joins read the current materialized row state of source tables
- run
:recomputefirst if source-table computed/reference values must be refreshed before the join
Dedicated :materialize-join limits:
- inline
:left {:rows ...}max:1000 - table-source window max per side:
10000 - output row max:
10000 - join key max:
4 - projected right-field max:
64
Table-resource limits to design against:
500table resources (families) per workspace50_000live rows per concrete table inside a family200columns per table16promoted/index fields per table128partitions per family16partitions touched per write12selected partitions per query/aggregate/export24selected partitions per preview/read/schema256max partition key bytes64 KBper cell256 KBmax row payload256 MBmax table size2 GBmax workspace table DB size1000rows per write1000rows per query page10000max query scan window viapage.offset + page.limit200max aggregate groups
Bounded aggregate reporting now also supports:
order-byon group keys or metric aliaseshas-moretruncation metadata- metric-local conditional filters
- scalar
arg-max/arg-min havingon grouped results- bounded
collect-set - date-bucket group specs for
day,week, andmonth - numeric-bin group specs
percentileandmediandistribution metrics
Current recommendation when one logical dataset approaches bounded-table limits:
- keep
50_000live rows per concrete table or partition as a real boundary - use first-class
:partitioningwhen the data naturally partitions by region, tenant, source, or a date bucket and most reads/writes stay within one partition or a small bounded subset - keep the family root as the schema/metadata owner and select partition scope explicitly for query-like operations
- use separate explicit tables when the data truly represents different datasets or lifecycles, not just as a workaround for missing partition support
- if authors mainly rely on wide cross-partition scans, arbitrary joins, or general database behavior, move the workload to a dedicated
:dbstep and an external database/query system instead of stretching bounded tables further
Security And Runtime Controls
| Control | Guidance |
|---|---|
| Webhook auth | Declare explicit :auth {:type ...} on webhook interfaces. |
| Secret handling | Store secret material in bindings, never in flow files. |
| Retry policy | Use :retry {:max-attempts ... :backoff-ms ...} for transient failures. |
| Runtime limits | Payload/output limits are enforced at runtime. |
| Persistence posture | Plan refs/:persist early: inline results should stay under 512 KB, hard step/DB/code/notify payload caps are 1 MB, and persisted blob writes cap at 50 MB retained or 4 GB ephemeral. |
High-Impact Limits
| Limit | Value | Notes |
|---|---|---|
| Flow payload (core definition map) | 150 KB | Excludes template/function body content. |
| Effective package budget with templates/functions | ~2.1 MB | Externalize large prompt/request/query content into templates/functions. |
| Max step result | 1 MB | Hard per-step output cap. |
| Inline result threshold | 512 KB | Exceeding this usually requires :persist. |
| Webhook payload max | 50 MB (signed multipart: 5 MB) | See webhook transport and signing mode details. |
Platform-managed LLM :max-tokens clamp | 10000 per call | Applies to platform-managed keys. |
| Wait timeout | Default 24h, max 30 days | Set explicit bounded timeout in wait configs. |
See full list: Limits And Recovery
CLI Commands That Surface These Features
| Command group | Use |
|---|---|
breyta flows pull/push/configure/configure check/validate | Authoring and configuration lifecycle. |
breyta flows release / breyta flows installations ... | Advanced rollout and installation targeting. |
breyta flows run <slug> --wait | Runtime verification after changes. |
breyta resources search / breyta resources workflow list / breyta resources read | Find and inspect persisted run artifacts and refs, including bounded preview reads for table resources. |
breyta resources table ... | Query, aggregate, export, import, and edit table resources from the CLI. |