Flow Configuration
Breyta configuration guide for wiring required connections, secrets, setup inputs, and per-run invocation inputs.
Goal
Connect a flow definition to real runtime systems without hardcoding credentials.
Quick Answer
Declare setup dependencies in :requires, per-run inputs in :invocations, then configure required setup values before first run:
-
Set up reusable workspace connections first (recommended): Connections First
-
breyta flows configure <slug> ...for the default draft target -
breyta flows configure <slug> --target live ...orbreyta flows installations configure <installation-id> ...for live/installation targets -
breyta flows configure check <slug>(or--target live --version latest) to verify required values are present before running
Requires, Bindings, And Runtime Resolution
| Layer | Stored where | Example | Notes |
|---|---|---|---|
| Requirement contract | Flow definition :requires | {:slot :orders-api :type :http-api ...} | Declares setup values and dependencies that must be configured. |
| Invocation contract | Flow definition :invocations | {:default {:inputs [{:name :question :type :text}]}} | Declares values supplied for each run. |
| Bound value | Target bindings (draft / live / installation) | orders-api -> conn-123 | Set by flows configure / installation config. |
| Runtime resolution | Selected run target | flows run <slug> or --target live | Run uses resolved bindings for that target. |
requires does not store connection ids directly.
Connection ids are stored in target bindings and resolved at run time.
LLM requirement note:
- For both
:llmand:agentsteps, the requirement type should be:http-api. - If a connection-create UI still shows
{:type :llm-provider :label "LLM Provider (Legacy)" :description "Legacy create mode normalized to HTTP API with an LLM backend" :icon :sparkles}, treat that as a legacy create-mode alias only. - The stored connection and the authored
:requirescontract should still use:http-api.
OpenAI connection shape
For OpenAI-backed :llm or :agent steps, prefer an :http-api requirement
with an OpenAI backend connection. The base URL should include /v1, and the
connection config must be a map, not nil.
{:requires [{:slot :ai
:type :http-api
:provided-by :author
:label "OpenAI"
:base-url "https://api.openai.com/v1"
:auth {:type :api-key}}]}
For an installer-owned OpenAI key, switch ownership:
{:requires [{:slot :ai
:type :http-api
:provided-by :installer
:label "OpenAI"
:base-url "https://api.openai.com/v1"
:auth {:type :api-key}}]}
When creating or repairing an OpenAI connection record directly, keep the same
shape: type http-api, backend openai, base URL
https://api.openai.com/v1, and a non-null config map. For raw HTTP requests to
OpenAI rather than the LLM provider backend, use bearer auth semantics for the
Authorization header, as in OpenAI's API reference.
AWS Bedrock Claude connection shape
For AWS Bedrock-backed :llm or :agent steps, use an :http-api
requirement with :backend :bedrock and AWS SigV4 auth. Store AWS credentials
in the referenced secret; keep region and service metadata in the connection
auth config.
{:requires [{:slot :ai
:type :http-api
:provided-by :author
:label "AWS Bedrock Claude"
:backend :bedrock
:base-url "https://bedrock-runtime.us-east-1.amazonaws.com"
:auth {:type :aws-sigv4
:secret-ref :aws-bedrock
:region "us-east-1"
:service "bedrock"}}]}
The secret payload should include:
{
"access-key-id": "AKIA...",
"secret-access-key": "...",
"session-token": "optional temporary credential token"
}
The hosted Bedrock backend is SigV4-only today. Amazon Bedrock API keys are a
separate bearer-token auth surface and are not accepted for this :backend :bedrock connection path yet.
Then call Bedrock through :llm with a Bedrock Claude model id:
(flow/step :llm :summarize
{:connection :ai
:model "anthropic.claude-3-5-sonnet-20241022-v2:0"
:prompt "Summarize the attached material."})
Use :provided-by :installer instead when each installer must connect their
own AWS account. If you need a raw Bedrock Runtime operation instead of a normal
Claude message call, use :http with the same :aws-sigv4 auth shape. For
Bedrock Claude Sonnet 4.5 and Haiku 4.5 model ids, choose either
:temperature or :top-p; do not set both on one request.
Public flow connection ownership
When a flow is public or marketplace-visible, every connection requirement,
including :type :secret requirements, must explicitly say who provides that
credential:
- Use
:provided-by :authorfor author-owned platform credentials, such as an
Apify or OpenAI account that should power every public install. - Use
:provided-by :installerfor user-owned accounts that each installer must
connect themselves, such as their own CRM, inbox, or OAuth account.
Do not rely on the default for public flows. An omitted :provided-by is treated
as installer-owned for compatibility, but public release checks require the
choice to be explicit so author-owned credentials are not accidentally presented
as installer setup work.
Example author-owned connection requirements:
{:requires [{:slot :apify
:type :http-api
:provided-by :author
:label "Apify"
:base-url "https://api.apify.com/v2"
:auth {:type :bearer}}
{:slot :recall
:type :http-api
:provided-by :author
:label "Recall.ai"
:base-url "https://us-east-1.recall.ai"
:auth {:type :token}}
{:slot :openai
:type :http-api
:provided-by :author
:label "OpenAI"
:auth {:type :api-key}}]}
Worker Requirements
Use {:kind :worker ...} inside :requires when the flow depends on external workers instead of only connection bindings or form inputs.
Example:
{:requires [{:kind :worker
:label "Security review workers"
:provided-by :author
:job-types ["codex-review" "codex-fix"]
:provider "breyta/jobs"}]}
Rules:
:provided-by :authormeans installers are not asked to supply the worker.:provided-by :installermeans the install surface blocks until the packaged worker/runtime is in place.- Use one worker requirement per capability or worker pool. A flow may declare more than one.
Display icon
Flow icon rendering can also be curated independently of runtime bindings.
primaryDisplayConnectionSlotis display-only metadata used anywhere the flow icon is rendered today.- In the UI this is shown as the flow's primary integration.
- Set it from the flow detail UI or with
breyta flows update <slug> --primary-display-connection-slot <selector>. - Clear it with
breyta flows update <slug> --primary-display-connection-slot ''. - Valid selectors come from declared
:requiresslots or normalized keys exposed by:connections. - If the saved selector is unset, missing, or no longer matches the flow, rendering falls back to the first explicit
:connectionsicon, then the first inferred:requiresicon. - This metadata is workspace-scoped authoring state, not part of the pulled flow source file.
Use breyta flows show <slug> --pretty to inspect the current primaryDisplayConnectionSlot.
When a flow exposes connection metadata, JSON responses also include _hints with the canonical inspect/set/clear commands.
Activation Inputs Behavior
Activation inputs are target-scoped setup values saved on the selected runtime target and reused on later runs until they are changed.
- Configure draft target inputs with
breyta flows configure <slug> --set activation.<key>=<value>. - Configure installation inputs with
breyta flows installations configure <installation-id> --input '{"<key>":"<value>"}'. - At run time, these saved setup values are applied automatically for that target.
Important lifecycle rule:
breyta flows release <slug>andbreyta flows promote <slug>may advance live and track-latest installations, but they do not clear previously configured activation inputs.- Update activation inputs explicitly through configure commands when you need to change them.
Blob-storage slots add a required setup root
Installer-owned :blob-storage slots always add a required setup control for the storage root.
Flow authors can customize its default/label/description through the slot config, but cannot disable it.
Example:
{:requires [{:slot :archive
:type :blob-storage
:label "Archive storage"
:config {:prefix {:label "Folder prefix"
:description "Stored under this folder in the selected storage connection."
:default "reports"
:placeholder "reports/customer-a"}}}]}
At setup time the installer binds the slot to a concrete storage connection.
For end-user installations, the authored prefix becomes a private effective root such as installations/<installation-id>/reports until the installer explicitly saves another root in bindings.<slot>.config.root.
Persisted blob writes and runtime resource pickers both reuse that same effective root automatically when they point at :slot :archive.
For platform storage, connected persists write under workspaces/<ws>/storage/<root>/....
They do not add any hidden persist/<flow>/<step>/<uuid> segments under that root.
What the installer configures:
- bind the slot itself to a blob-storage connection such as
platform - optionally choose an explicit storage root override when this installation should use a shared lane instead of its default private lane
For an end-user installation with id inst-1, the default effective root for the example above is:
installations/inst-1/reports
That means a later step like:
:persist {:type :blob
:slot :archive
:path "exports/{{input.customer-id}}"
:filename "summary.pdf"}
writes to:
workspaces/<ws>/storage/installations/prof-1/reports/exports/<customer-id>/summary.pdf
If the installer later saves an explicit shared root such as reports/customer-a, that saved root overrides the private default.
The flow author never repeats the root in the step itself. The installer owns that root once, and both persistence and runtime pickers reuse it automatically. That full path is the final object path for the connected write.
Blob-storage sharing is installer-configured
Blob storage is defined by local :requires slots, and sharing happens only when installers point different flows at the same concrete storage location.
By default, two end-user installations stay isolated because each one derives its own private root, such as installations/<producer-installation-id>/reports and installations/<consumer-installation-id>/reports.
Producer flow:
{:requires [{:slot :archive
:type :blob-storage
:label "Archive storage"
:config {:prefix {:default "reports"}}}]}
Consumer flow:
{:requires [{:slot :archive
:type :blob-storage
:label "Archive storage"
:prefers [{:flow :daily-report-export
:slot :archive}]
:config {:prefix {:default "reports"}}}]
:invocations {:default
{:inputs [{:name :report
:type :resource
:slot :archive}]}}}
Installer setup decides whether those two flows share only when both installations explicitly converge on the same root:
{:bindings {:archive {:binding-type :connection
:connection-id "platform"
:config {:root "reports/customer-a"}}}}
If both installations use the same backend and the same root, the consumer picker sees the producer's persisted files. If they use different roots, they stay isolated.
:prefers is only an intent hint. It does not create a hard runtime binding, and it does not replace the installation-scoped default root.
To share, the installer must still explicitly save the same connection + root on both flows.
Notes:
- blob-storage intent lives in local
:requires, not in top-level:connections :persist :slotalways points at a local blob-storage slot such as:archive- runtime resource pickers reuse the resolved
connection + rootbehind that local slot - end-user installations derive a private default root from the authored prefix; shared roots require an explicit override
- local slot names are just author intent; the installer-controlled storage root is the actual sharing boundary
- two flows may use different local slot names and still share if installers point them at the same concrete location
Setup Forms And Invocation Inputs
Use :requires with {:kind :form ...} for setup values saved on the selected
target. Use :invocations for values supplied each time the flow is run.
For public/end-user flows, plan setup page and run form fields before authoring
behavior. A common failure mode is "CLI works but UI fails": the agent passed
JSON directly through breyta flows run, but the setup page, run form fields,
resource picker, upload CSV control, or installed flow live target used a
different input shape or an old live version.
Classify every value before adding fields:
- setup-once: saved installation context such as company profile, tone, examples, default folder, region, or durable voice/profile/bible inputs
- run-each-time: question, CSV, uploaded file, resource picker selection, row limit, date range, recipient, or prompt
- connection: accounts, OAuth/API keys, databases, blob-storage roots
- hidden/internal: ids, debug toggles, author-only workspace state, and values that should not be exposed to non-technical users
Connection credentials are setup objects, not chat text. If no healthy
third-party connection exists, hand off the Breyta setup/edit URL before building
that integration.
Keep public forms minimal. Use clear labels, helper text, defaults, :label
for the run/recent-runs section, and :title for each recent-run row. Move
advanced or internal settings into author-provided config instead of surfacing
them as end-user fields.
| Contract piece | Meaning |
|---|---|
setup form in :requires | Enter once during setup and reuse on later runs until changed |
:invocations | Ask for the value each time the flow is run |
:provided-by :installer | End user must supply the setup value |
:provided-by :author | Flow author supplies the setup value; the installer is not prompted |
Setup form example:
{:requires [{:kind :form
:fields [{:key :region
:label "Region"
:field-type :select
:required true
:options ["EU" "US"]}]}]}
Per-run input example:
{:requires [{:slot :archive
:type :blob-storage
:label "Archive storage"}]
:invocations {:default
{:label "Run input"
:inputs [{:name :question
:label "Question"
:type :text
:required true}
{:name :resources
:label "Resources"
:type :resource
:slot :archive
:required true
:multiple true
:accept ["application/pdf" "text/plain"]}]}}
:interfaces {:manual [{:id :run
:label "Run"
:invocation :default
:enabled true}]}}
Resource input expectations:
- use invocation input
:type :resource - the flow receives resource references, not file contents
- the default resource picker filter is
:file, which covers uploads and persisted blobs - run-page uploads are finalized as workspace
:fileresources and auto-selected for the current run :acceptsupports exact MIME types like"application/pdf"and wildcard prefixes like"text/*"- add
:resource-typesonly when you need something other than the default file picker, then combine it with:acceptif needed :multiple truelets the user append uploaded files to the current selection:slot <slot>scopes the picker to a local blob-storage slot declared in:requires- runtime pickers share artifacts when installers configure different flows to the same concrete storage location
For text-oriented flows, prefer a MIME allowlist like:
{:name :resources
:label "Text resources"
:type :resource
:required true
:multiple true
:accept ["text/*"
"application/json"
"application/xml"
"application/edn"]}
Inside the flow:
'(let [input (flow/input)]
{:region (:region input)
:question (:question input)
:resources (:resources input)})
Example input seen by the flow:
{:region "EU"
:question "Summarize the attached reports"
:resources [{:type :resource-ref
:uri "res://v1/ws/ws-1/file/report-a"}
{:type :resource-ref
:uri "res://v1/ws/ws-1/result/summary-b"}]}
Deprecated compatibility:
{:kind :form :collect :setup ...}is still accepted, but setup is already the default.{:kind :form :collect :run ...}is still accepted for old flows, but new per-run fields should be written under:invocations.- older manual-run field shapes are still accepted for old flows, but new manual-run fields should be written under
:invocationsand exposed with:interfaces :manual.
Binding-Based Usage And Delete Guards
breyta connections usages and breyta secrets usages report where bindings currently reference each id.
Delete commands are guarded by those binding references:
breyta connections delete <connection-id>breyta secrets delete <secret-id>
If any draft, live, or installation target still references the id, delete is blocked until wiring is moved/unset.
1. Declare Requires In The Flow
{:requires [{:slot :orders-api
:type :http-api
:label "Orders API"
:base-url "https://api.example.com"
:auth {:type :bearer}}
{:slot :risk-api-key
:type :secret
:label "Risk API Key"}]}
requires declare what must be configured before successful runtime execution.
Legacy {:kind :form :collect :run ...} requirements still support:
:labelfor the recent-runs heading:titleas a run input keyword or fixed string
New flows should use :invocations for run inputs.
OAuth-capable HTTP API slots
When a flow requires a delegated OAuth account, declare the slot as an :http-api requirement with an :oauth contract.
Example:
{:requires [{:slot :x
:type :http-api
:label "X account"
:base-url "https://api.x.com/2"
:oauth {:provider :any
:scopes {:required ["tweet.write"
"tweet.read"
"users.read"]
:any-of ["offline.access"]}
:requires-refresh-token true}}]}
Setup behavior:
- the setup sidepeek renders a connect-first account card instead of only a saved-connection dropdown
- the connect action carries the installation context, flow slug, connection slot, safe return path, required scopes, base URL hints, and refresh-token requirement
- users can still reuse a compatible existing OAuth connection from the fallback selector
- custom OAuth providers are author-defined templates on the slot; setup can render a provider connect button without adding a provider registry entry
- for cross-workspace installs, Breyta copies that template into a pending installer-owned connection before redirecting to the provider login page
- installers cannot create or edit custom OAuth provider metadata from setup
- after OAuth completes with installation setup context, Breyta saves the slot binding on the installation before returning to setup
- OAuth setup return paths must stay inside the workspace; external
return-urlvalues are rejected - setup page loads are read-only; they do not mutate bindings from URL parameters
Connection slots are required by default.
- Optional slot:
:optional trueor:required false. - Optional slots still render in setup.
- Runtime code should check that an optional binding exists before using it.
Custom OAuth slots use :oauth {:provider :custom ...} plus an author-owned template connection. During install, Breyta clones that template into an installer-owned pending connection and redirects to the provider.
Template authorization is version-pinned. If the template ID or OAuth metadata changes, release a new flow version and update/recreate installations.
Example custom OAuth slot:
{:requires [{:slot :tiktok
:type :http-api
:provided-by :installer
:label "TikTok account"
:base-url "https://open.tiktokapis.com"
:oauth {:provider :custom
:provider-name "TikTok"
:template-connection-id "conn-tiktok-template"
:scopes {:required ["user.info.basic"]}
:requires-refresh-token true}}]}
What end users still need from provider docs:
- authorize URL
- token URL
- callback/redirect URI registration
- probe URL for health checks
- client ID and client secret
- any required extra authorize params
- whether PKCE must be enabled
Recommended author/operator guidance for custom OAuth slots:
- keep
:labelspecific, for exampleX accountinstead ofOAuth - include the expected API
:base-urlin the slot requirement when it is known - keep
:oauth.scopes.requiredas small as possible - include
:oauth.requires-refresh-token truewhen the flow expects long-lived reuse - use
:oauth.authorize-params/:oauth.scope-separatorfor non-standard authorize parameters - use
:oauth.token-params/:oauth.refresh-token-paramsfor non-standard token parameters - keep scopes as separate values on the connection; Breyta joins them with the configured separator when building the provider login URL
- release a new flow version and ask installers to reconnect after changing template authorize or token parameter mappings
- ask installers to reconnect if a legacy public install has a custom OAuth clone in the creator workspace
Example operator handoff note for creating the author-owned custom OAuth template:
Configure an OAuth app at the provider and register:
https://<your-breyta-base-url>/api/oauth/callback
Legacy custom OAuth installs can still use:
https://<your-breyta-base-url>/<workspace-id>/api/oauth/callback
Then create an OAuth account connection template in Breyta with:
- Base URL: https://api.x.com/2
- Authorize URL: https://x.com/i/oauth2/authorize
- Token URL: https://api.x.com/2/oauth2/token
- Probe URL: https://api.x.com/2/users/me
- Scopes: tweet.write tweet.read users.read offline.access
- PKCE: enabled
Current product limitation:
- The custom OAuth connection form currently expects both a client ID and client secret, so public-client-only provider setups are not yet supported through this path.
2. Configuration Targets
draft and live resolve the same flow definition schema through different runtime targets:
| Aspect | Workspace draft | Installation live / explicit installation |
|---|---|---|
| Definition used at runtime | Latest pushed working copy | Installed released version |
| Configure command | breyta flows configure <slug> --set ... | breyta flows configure <slug> --target live --version <n|latest> --set ... or breyta flows installations configure <installation-id> --input ... |
| Check command | breyta flows configure check <slug> | breyta flows configure check <slug> --target live --version <n|latest> (or inspect/install specific target separately) |
| Webhook endpoint behavior | Flow-source scoped target with draft in generated endpoint paths | Flow-source scoped author testing with live in generated endpoint paths, or installation-scoped consumer endpoints |
| Run command | breyta flows run <slug> --wait | breyta flows run <slug> --target live --wait or --installation-id <id> |
| Primary use | Build/test iteration | Stable installed behavior |
| Context | What it controls | Canonical commands guide |
|---|---|---|
| Workspace default target | Values used by breyta flows run <slug> with no explicit installation target | CLI Workflow |
Installation target (live or explicit installation id) | Installation-specific values plus schedule/interface behavior | Installations |
Use connection reuse as a default operating rule (breyta connections list before creating new connections).
When validating installation-target configuration, run with explicit installation targeting:
breyta flows run <slug> --target live --wait- or
breyta flows run <slug> --installation-id <installation-id> --wait
Live version pinning and slot migrations
When a new release adds or renames required slots, pin configuration to the version you plan to run:
breyta flows configure check <slug> --target live --version <n|latest>breyta flows configure <slug> --target live --version <n|latest> --set <slot>.conn=<connection-id>breyta flows configure <slug> --target live --from-draft --version <n|latest>
This avoids validating live updates against stale profile-version requirements.
For a brand-new flow with required slots, use the same version-pinned live commands before the first release. Without --version latest, live checks can miss unresolved slots and flows release <slug> can fail with live_config_incomplete.
3. Secret Handling Rules
- never hardcode secrets in flow files
- prefer Breyta connection/setup UI secret fields for human-entered values
- keep secret values in bindings/target input, not flow definitions or chat
- rotate by updating bindings, then re-running
- keep placeholder semantics in generated templates (
:redacted,:generate) - use CLI secret/config commands only for automation or explicit CLI setup
For secret lifecycle commands and examples, use Secrets.
Frequently Asked Questions
What is the difference between workspace configuration and installation configuration?
In the canonical surface, use draft target configuration (flows configure) and installation configuration (flows installations configure).
Legacy bindings commands are compatibility behavior.
How do I rotate a secret without changing flow code?
Update the configured secret value for the target you run against, then re-run the flow (see Secrets).
Common Pitfalls
- running before required config is applied
- using configuration commands after a failed push
- mixing workspace-target config with installation-target config
- creating duplicate connections during iteration instead of reusing existing ones