Installations (End-User Flows)
Quick Answer
Use this guide to model, create, enable, and operate end-user flow installations with installation-scoped inputs and uploads.
An "end-user flow" is a flow intended to be used by others in the workspace.
Public discover visibility and released installable interfaces gate discover and
install surfaces.
An "installation" is a per-user instance of an end-user flow, backed by an internal
runtime target owned by the subscribing user.
Public Discover Visibility
If an end-user flow should appear in discover/install surfaces, set public discover visibility explicitly.
Before enabling it, ask the flow author to confirm that the flow may be made
accessible to all Breyta users and is ready for end-user installation.
Authoring options:
{:discover {:public true}
...}
or after push:
breyta flows discover update <slug> --public=true
Requirements:
- public discover visibility must be enabled
- the flow must have an active released version
Checklist to make a flow show up in Discover:
- Choose one visibility path:
- Source-first: add
:discover {:public true}to the flow definition before push only after explicit approval. - Explicit after push: push first, then run
breyta flows discover update <slug> --public=trueonly after explicit approval.
- Source-first: add
- Push the flow with
breyta flows push --file .... - If you chose the explicit path, run
breyta flows discover update <slug> --public=trueafter push. - Release/promote it so there is an installable live version.
- Verify the web Discover install dialog. For CLI indexing checks, use another
workspace or--include-ownonly while debugging the owner workspace.
Webhook-interface flows can be installable when auth and setup are explicit.
For public flows, keep a manual interface available while validating webhook or
schedule behavior.
Use breyta flows show <slug> --pretty to confirm stored metadata includes discover.public.
Use breyta flows discover list or breyta flows discover search <query> to browse the same public installable catalog the web app shows.
This is different from breyta flows search <query>, which searches actual workspace flow metadata, and from breyta flows templates search <query>, which searches approved reusable templates to inspect and copy from.
For paid public flows and Stripe seller onboarding, see Paid Public Flows.
Activation Vs Install
The activation/setup URL and the Discover install surface are different.
| Surface | Proves | Does not prove |
|---|---|---|
/activate or configure/check | The owner workspace or selected runtime target has required bindings and setup inputs. | That an end user can discover, install, configure, and run the flow. |
| Discover install dialog | The flow is exposed as an installable end-user flow. | That the installed target has all installer-owned connections and inputs bound. |
| Installation run | The installer-scoped target can execute with its saved bindings and inputs. | That public copy, pricing, or media were reviewed. |
For installable-flow work, do not stop at /activate. Verify the full end-user
path: release/promote the live version after approval, open the Discover install
dialog when browser access is available, create/configure/enable an installation,
bind installer-owned connections and setup inputs, and run the installed target:
breyta flows installations create <slug> --name "Smoke install"
breyta flows installations configure <installation-id> --input '{"<field>":"<value>"}'
breyta flows installations enable <installation-id>
breyta flows run <slug> --installation-id <installation-id> --wait
If browser access is unavailable, keep the CLI installation proof and report
web UI not verified.
Install Surface Copy
Installable flows can expose separate install-facing markdown copy from their normal flow description.
Use:
breyta flows update <slug> --publish-description-file ./publish-description.md
or:
breyta flows update <slug> --publish-description "## Install this flow"
Behavior:
| Surface | Copy source |
|---|---|
| Discover/install dialog | publishDescription when set |
| Discover/install dialog fallback | normal flow description when publishDescription is empty |
| Discover card/list preview | normal flow description |
| Normal setup/run page | normal flow description |
This lets you keep a short catalog summary while showing richer installation guidance in the dialog itself.
Normal setup and run pages render the flow description as sanitized Markdown.
Use this for operational guidance that must stay visible after install, such as
opening a related setup flow before running:
{:description "Open [Voice Profile Writer](/workspace/flows/voice-profile-writer/launch) before running outreach."}
Raw https://... URLs are also autolinked. Unsafe HTML, scripts, and event
handler attributes are stripped before rendering.
Discover Card Media
Public discover/install cards can also show curated image or video media:
breyta flows update <slug> \
--publish-media-type image \
--publish-media-source-kind https-url \
--publish-media-source https://cdn.example.com/hero.png \
--publish-media-alt "Preview of the generated result"
Notes:
publishMediareplaces the whole stored discover card media value- Use
--clear-publish-mediato remove it - For video, add
--publish-media-poster-kindand--publish-media-posterfor an optional poster - If you keep flow metadata in source control, you can also author the same value as
:publish-mediain the flow file and push it
Core Concepts
| Term | Meaning |
|---|---|
| End-user flow | Flow intended for subscribers. |
| Installation | Per-user runtime target instance for an end-user flow. |
| Activation inputs | Setup values the installer enters once and reuses on later runs. Installers can change them by reconfiguring setup. |
| Promote/release behavior | flows promote/flows release advance live and track-latest installations, but do not clear previously configured setup values. |
Creator: declare worker requirements
Use {:kind :worker ...} inside :requires when a flow depends on external workers.
Examples:
{:requires [{:kind :worker
:label "Security review workers"
:provided-by :author
:capability "codex-agent"
:job-types ["codex-security-review" "codex-security-fix"]
:provider "breyta/jobs"
:version "v1"}]}
{:requires [{:kind :worker
:label "Customer sync worker"
:provided-by :installer
:capability "customer-sync"
:job-types ["customer-sync"]
:provider "acme/customer-sync-worker"
:setup-summary "Deploy the packaged worker before enabling this installation."
:setup-docs-url "https://docs.example.com/customer-sync-worker"}]}
Install behavior is derived from those requirements:
- no worker requirements: native runtime only
- author-provided worker requirements: managed external workers without extra installer setup
- installer-provided worker requirements: packaged worker/runtime required before launch
- prefer
:job-typesin authored worker requirements;:job-typeis accepted as a single-type alias
Rule of thumb:
- put installer-owned config and setup fields in
:requires - put external worker dependencies in
:requiresas{:kind :worker ...}
If installers must run a worker, provide a packaged deployment story and setup docs rather than raw jobs protocol details.
Creator: declare activation inputs (setup form)
Installation setup inputs are declared via :requires with a {:kind :form ...}
requirement. The installer fills them out once during setup and the values are
reused on later runs until the installation is reconfigured.
Use :invocations when an input must be provided on each manual run instead of
being saved on the installation. Invocation inputs do not block setup; they
render directly in the launch/run form.
Use :provided-by :author on a requirement when it is satisfied by the flow
author/workspace owner instead of the installation user. Author-provided
requirements do not block end-user setup or /launch.
Creator: declare installer-owned OAuth connections
For delegated accounts that the installer must connect, declare an installer-owned
:http-api slot with an :oauth contract. The setup sidepeek renders that slot
as a connect-first account card, with a fallback selector for existing workspace
connections. For custom OAuth, the author defines the provider template on the
slot; setup renders a connect button for that provider but does not let
installers create or edit custom OAuth metadata.
{:requires [{:slot :crm
:type :http-api
:provided-by :installer
:label "CRM account"
:base-url "https://api.example.com"
:oauth {:provider :any
:scopes {:required ["contacts.read"]}
:requires-refresh-token true}}]}
Author-defined custom provider example:
{: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}}]}
Connection slots are required by default. Mark a slot optional with
:optional true; :required false is also accepted as the inverse setup alias.
Optional slots still appear in setup so installers can connect them, but they do
not block activation or launch when left unbound. The flow should only execute
steps for an optional slot after checking that the corresponding binding exists.
When the installer clicks a custom OAuth template button, Breyta creates a
pending installer-owned connection from the author template, clears token fields,
and redirects to the provider login page. When the installer completes OAuth
from setup, Breyta saves the concrete connection binding on that installation
before returning to setup.
For public flows, the cloned connection and token secrets live in the
installer's workspace. Runtime uses the installation binding to read and refresh
that installer-owned connection; it does not copy installer tokens into the
creator workspace. If an older public installation created a custom OAuth
connection in the creator workspace, have the installer reconnect the slot from
setup so the connection is recreated in the installer workspace.
Setup GET routes remain read-only, and OAuth return paths must be relative
workspace paths.
Template authorization is release-version based. The template connection is
usable by installers only when the installed flow version declares that exact
:template-connection-id on the requested :requires slot. There is no separate
connection-level publish/share flag today; after changing a template ID or slot
OAuth metadata, release a new flow version and update or recreate installations
so the setup sidepeek and OAuth connect handler read the same pinned version.
Some custom providers use non-standard authorize URLs or token parameter names.
Keep the provider out of the registry and put the mapping on the custom OAuth
template connection config. Use :authorize-params for the provider login URL,
:scope-separator when the provider does not accept space-separated scopes,
:token-params for the authorization-code exchange, and
:refresh-token-params for refresh. Keep scopes as discrete scope values on the
connection; Breyta joins them with the configured separator when it builds the
authorize URL. For example, providers that require client_key instead of
client_id and comma-separated authorize scopes can use:
{:oauth {:authorize-params {:client-id "client_key"}
:scope-separator ","
:token-params {:client-id "client_key"}
:refresh-token-params {:client-id "client_key"}}}
Only enable PKCE when the provider's web OAuth docs require it. Some providers
support PKCE only for mobile or desktop clients, while their web token exchange
rejects code_verifier. In that case set :oauth {:pkce {:enabled false}} on
the template connection before installers connect.
After changing authorize or token parameter mappings, release a new flow version
and have installers reconnect the slot so Breyta creates a fresh installer-owned
connection and stores a usable token secret.
Example:
{:requires [{:kind :form
:label "Setup"
:fields [{:key :region
:label "Region"
:field-type :select
:required true
:options ["EU" "US"]}]}]}
{:invocations {:default
{:label "Run input"
:inputs [{:name :question
:label "Question"
:type :text
:required true}]}}}
{:requires [{:slot :ai
:type :http-api
:label "LLM Provider"
:auth {:type :api-key}
:provided-by :author}]}
If you are maintaining an older flow that uses run-collected form requirements,
:label customizes the recent-runs heading and :title customizes each recent
run row. New flows should put per-run fields in :invocations.
Example:
{:requires [{:kind :form
:collect :run
:label "Open questions"
:title :question
:fields [{:key :question
:label "Question"
:field-type :text
:required true}]}]}
Behavior:
:labelchanges the section heading fromPrevious runs/Recent runsto your chosen text.:titlechanges each recent-run row title. Use a keyword to pull from a field in the run input, or a string for fixed text.- If neither is set, the UI keeps the existing fallback copy.
Supported setup form field types:
:field-type | What the installer sees | Typical use |
|---|---|---|
:text | Single-line text input | IDs, names, short prompts |
:textarea | Multi-line text box | Longer instructions or prompts |
:select | Dropdown | Region, mode, model choice |
:boolean | Checkbox | Confirm/enable flags |
:number | Numeric input | Limits, counts, thresholds |
:date | Date picker | Scheduled/reporting dates |
:time | Time picker | Clock time inputs |
:datetime | Date-time picker | Combined schedule inputs |
Invocation resource input notes:
- Use invocation input
:type :resourcefor new flows. - The run UI shows
Add resourcesfor existing workspace resources only when a resource input is declared. - Local upload is available on the run page as a separate
Upload file from computerorUpload files from computeraction when the field allows:fileresources.:fileis the default resource type. - Files uploaded from the run page are stored as normal workspace
:fileresources and auto-selected for the current run, so they also appear in later picker searches. - Use
:multiple truewhen the flow expects a collection of resources. - Resource fields default to
:file, which covers uploads and persisted blobs. - Use
:resource-typesand:acceptonly when you need to narrow what the user can select. :acceptsupports exact MIME types like"application/pdf"and wildcard prefixes like"text/*". The same allowlist is used for both picker filtering and the browser file chooser on local uploads.- When both
:resource-typesand:acceptare present, the selected resource must satisfy both filters. - For single-value resource fields, a local upload replaces the current selection. With
:multiple true, new uploads are added to the current selection.
Example:
{:invocations {:default
{:label "Run input"
:inputs [{:name :resources
:label "Resources"
:type :resource
:required true
:multiple true
:accept ["application/pdf" "text/plain"]}]}}}
Text-focused example:
{:invocations {:default
{:label "Run input"
:inputs [{:name :resources
:label "Text resources"
:type :resource
:required true
:multiple true
:accept ["text/*"
"application/json"
"application/xml"
"application/edn"]}]}}}
Manual upload recipes for installed flows
Use resource invocation fields for normal manual installed runs. This renders a
resource picker plus local upload action on the installed run page:
{:invocations {:default
{:label "Analyze images"
:inputs [{:name :images
:label "Images"
:type :resource
:required true
:multiple true
:accept ["image/*"]}]}}
:interfaces {:manual [{:id :run
:label "Analyze images"
:invocation :default
:enabled true}]}}
Use webhook file inputs when the installed flow should receive multipart file
uploads from an integration or from the CLI installations upload command:
{:invocations {:default
{:inputs [{:name :files
:type :file
:required true
:multiple true}]}}
:interfaces {:webhook [{:id :file-upload
:invocation :default
:event-name "files.upload"
:auth {:type :hmac-sha256
:header "X-Signature"
:secret-ref :webhook-secret}}]}}
For older flows, {:kind :form :collect :run ... :field-type :resource} and
event triggers with :config {:fields [{:name :files :type :file ...}]} remain
compatibility shapes. New source should prefer :invocations plus explicit
:interfaces.
Inside the flow: how setup and invocation fields appear in input
Setup form fields and invocation inputs become part of flow/input.
- setup fields are available on every run after setup is saved
- invocation inputs are available only for the current run
- if both exist, the flow sees one combined input map
Example:
{:requires [{:kind :form
:fields [{:key :region
:field-type :select
:required true
:options ["EU" "US"]}]}]
:invocations {:default
{:inputs [{:name :question
:type :text
:required true}
{:name :resources
:type :resource
:required true
:multiple true}]}}
:flow
'(let [input (flow/input)
region (:region input)
question (:question input)
resources (:resources input)]
{:region region
:question question
:resource-count (count resources)})}
The run input seen by the flow looks like:
{:region "EU"
:question "What changed since last week?"
: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"}]}
Creator: declare per-run inputs for manual and webhook interfaces
For manual and webhook runs, declare per-run inputs in :invocations. Point
manual interfaces at the invocation, and expose webhook ingress through
:interfaces :webhook.
For manual interfaces, the interface :label is reused as the primary
manual-run CTA on flow, installation, launch, and setup surfaces.
For webhook interfaces, the interface owns delivery details such as
:event-name, auth, replay, and endpoint metadata. Older webhook definitions
remain accepted for existing flows, but new webhook ingress should be moved to
:interfaces :webhook.
Older manual field definitions remain accepted for existing flows, but new
manual-run fields should be written in :invocations.
Invocation fields are validated and can be used to:
| Use | Details |
|---|---|
| Run form UX hints | Tell UI/CLI which inputs to request. |
| Resource/file input shape | Enable resource selection or multi-file upload with :multiple true. |
Example (manual run):
{:invocations {:default
{:inputs [{:name :question
:type :text
:label "Question"
:required true}]}}
:interfaces {:manual [{:id :ask
:label "Ask"
:invocation :default
:enabled true}]}}
Example (webhook upload):
{:invocations {:default
{:inputs [{:name :files
:type :file
:required true
:multiple true}]}}
:interfaces {:webhook [{:id :file-upload
:invocation :default
:event-name "files.upload"
:auth {:type :hmac-sha256
:header "X-Signature"
:secret-ref :webhook-secret}}]}}
Notes:
| Note | Behavior |
|---|---|
:file, :blob, and :blob-ref field types | Normalized as :blob-ref in validation. |
| Multipart ingestion | Flows-api persists file parts and passes blob-ref maps (for example {:path \"...\" :size-bytes 123 ...}). |
Creator: schedule setup controls
Top-level schedules bind one cron expression to the authored flow version.
That is not enough for public paid flows where each installer expects to choose
whether the flow runs weekly, monthly, in their timezone, or not at all.
Every schedule renders installer-facing setup controls. The schedule's :cron
and :timezone are the flow defaults:
{:schedules [{:id :weekly-review
:label "Scheduled security review"
:invocation :default
:enabled false
:cron "0 9 * * MON"
:timezone "UTC"}]}
Author setup can save author schedule overrides. Those overrides become the
inherited default for installers. Installer setup then stores installation-owned
schedule settings without mutating the authored schedule.
Current behavior:
| Field | Behavior |
|---|---|
| Schedules | Render schedule controls inside setup. |
| Saved choices | Stored as installation schedule settings. New installed schedules start disabled until enabled from setup, schedule card, or CLI. |
| Author setup | Author setup uses the same controls. Reset removes the author override and restores authored defaults. |
| Time/day overrides | Daily/weekly/monthly cadences expose bounded time/day controls. Monthly last uses a guarded 28-31 cron window. |
| Timezone control | Setup shows common IANA zones. Saved/default zones win; detected zones fill gaps. |
| Source schedule config | Setup does not rewrite authored :cron or :timezone; schedule sync/runtime must consume the saved schedule settings when applying installer-specific schedules. |
| Zero-setup install | Schedule controls count as installer setup for automatic runs. The installed flow page shows a schedule info card instead of a global activation action. |
| Installation runability | enable / disable guards all installation runs. Saved setup and per-interface/schedule settings are preserved. |
| Automatic execution state | Schedules/webhooks also have per-surface enablement. Authored :enabled false stops that surface. |
| Webhook interfaces | Setup renders one switch per webhook interface. On materializes/resumes; off disables that endpoint. |
Authors can update their own default schedule overrides from the CLI:
breyta flows configure my-flow \
--schedules '{"weekly-review":{"enabled":false,"preset":"monthly","timezone":"Europe/Oslo","hour":14,"minute":30,"dayOfMonth":"last"}}'
To change only enablement, use the shorthand flags:
breyta flows configure my-flow --schedule-disable weekly-review
breyta flows configure my-flow --schedule-enable weekly-review
To remove an author override and inherit the flow schedule default again:
breyta flows configure my-flow --schedule-reset weekly-review
Installers can update an installation schedule the same way:
breyta flows installations configure <installation-id> \
--schedules '{"weekly-review":{"enabled":true,"preset":"weekly","timezone":"Europe/Oslo","hour":9,"minute":0,"dayOfWeek":"MON"}}'
They can also enable, disable, or reset one schedule:
breyta flows installations configure <installation-id> --schedule-enable weekly-review
breyta flows installations configure <installation-id> --schedule-disable weekly-review
breyta flows installations configure <installation-id> --schedule-reset weekly-review
Use the schedule id as the schedule key. The accepted fields are enabled,
preset (daily, weekly, or monthly), timezone, hour, minute,
dayOfWeek, and dayOfMonth (first, mid-month, or last).
Recommended launch rule: use this for flows where schedule choices are part of
setup. For paid public flows, keep a manual run path available while validating
the schedule runtime path for installer-specific timezone, pause/resume, and
failure visibility.
End user: subscribe + configure automation (CLI)
| Step | Command |
|---|---|
| 1. Create installation | breyta flows installations create <flow-slug> --name "My setup" |
2. Provide activation inputs from :requires form fields | breyta flows installations configure <installation-id> --input '{"region":"EU"}' |
| 3. Enable the installation when setup is ready | breyta flows installations enable <installation-id> |
| 4. Inspect manual/HTTP/webhook/MCP invocation interfaces | breyta flows installations interfaces <installation-id> |
Creating or configuring an installation does not opt the installer into runs or
automatic execution. Manual runs, interface calls, and schedules are accepted
only after the installation is enabled. Automatic execution still also requires
the relevant webhook interface or schedule to be enabled in Setup, from the
installed flow page, or through the CLI/API schedule and interface controls.
Schedule controls count as setup for automatic runs because the installer must
explicitly choose whether each schedule is on, plus its frequency and timezone.
When automatic trigger enablement is requested before required setup is complete,
the API returns setup-state details that identify missing setup fields, missing
connection/blob-storage bindings, or invalid connection-backed choices.
If the installed flow declares an HTTP interface, smoke-test it with:
breyta flows interfaces call <flow-slug> <http-interface-id> \
--installation-id <installation-id> \
--input '{"key":"value"}' \
--wait
Without --wait, accepted long-running interface calls include data.statusUrl
when a workflowId is available. That status URL stays scoped to the same
installation/interface endpoint.
Installed interface endpoints use this shape:
/api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}
The workspace-scoped form
/api/workspaces/{workspace-id}/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}
remains available as an alternate compatibility URL.
Use an installed public flow from another flow or agent
Installed HTTP and MCP interfaces are the preferred reuse surface when another
flow, coding agent, or external client wants to call a public Breyta flow. Use
the installation-scoped endpoint rather than an author draft/live endpoint so
the caller's setup, auth, billing entitlement, and installed-flow ownership are
applied correctly.
Typical consumer flow:
- Find the public flow in Discover or with
breyta flows discover search. - Create, configure, and enable an installation.
- Inspect callable surfaces with:
breyta flows installations interfaces <installation-id>
- Smoke-test the installed HTTP interface:
breyta flows interfaces call <flow-slug> <http-interface-id> \
--installation-id <installation-id> \
--input '{"prompt":"A clean product image","quality":"low"}' \
--wait
- Use the installed endpoint from the wrapper flow, client, or agent:
/api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}
The workspace-scoped alternate is also accepted:
/api/workspaces/{workspace-id}/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}
HTTP clients should send the normal workspace/API authorization header:
Authorization: Bearer <BREYTA_TOKEN>
MCP clients use the same installed interface endpoint with Streamable HTTP.
Connect with bearer auth, call initialize, then tools/list, then
tools/call with the published MCP tool name. A successful tools/call starts
the backing flow run and returns the result plus Breyta run metadata.
Paid public flows can reject installed interface calls when entitlement or trial
runs are exhausted. Treat billing errors such as billing_trial_ended as
entitlement state, not as evidence that the HTTP or MCP interface is broken.
Inspect aggregate invocation metrics for that installation with:
breyta flows metrics <flow-slug> --installation-id <installation-id>
Metrics are redacted counters and timings for flow invocation surfaces. They do
not contain request bodies, response bodies, tokens, headers, secrets, or binding
values. Installed calls are reported with interfaceScope: "installation";
author-owned draft/live interface calls use interfaceScope: "draft" or
"live" and stay in separate aggregates.
End user: upload files (CLI)
| Step | Command |
|---|---|
| 1. Inspect installation interfaces and webhook endpoints | breyta flows installations interfaces <installation-id> |
| 2. Upload one or more files to webhook interface | breyta flows installations upload <installation-id> --file ./a.pdf --file ./b.pdf |
If the webhook invocation declares exactly one field of type
file/blob/blob-ref, the CLI infers the multipart field name automatically;
otherwise pass it:
breyta flows installations upload <installation-id> --file-field files --file ./a.pdf
Installable Agent Flows
Agent-based flows have additional author/installer boundary concerns. Use
this section as a checklist when preparing an agent flow for installation.
Who provides the LLM connection?
| Pattern | :requires config | Who pays | Installer setup |
|---|---|---|---|
| Author pays | {:slot :ai :type :http-api :provided-by :author} | Author | None — connection is pre-configured |
| Installer pays | {:slot :ai :type :http-api :label "LLM Provider" :auth {:type :api-key}} | Installer | Installer enters their API key during setup |
For most installable agent flows, author-provided is simpler — the
installer never sees an API key prompt. Use installer-provided when you want
the installer to control their own model/spend.
How to handle agent memory
Agent memory uses a :table resource. In an installed flow, the memory table
must be owned by the author's workspace (it's internal working state, not
installer-visible output).
Best practice: Create the memory table in the flow itself using :persist,
and pass the resulting ref to the agent step. Do not hardcode a table URI
from your authoring workspace.
{:flow
'(let [;; The flow creates/reuses its own memory table on each run.
;; The platform routes this to the flow's workspace (author's).
memory-ref (flow/step :table :ensure-memory
{:op :schema
:persist {:type :table :name "agent-memory"}})
result (flow/step :agent :review
{:objective "Review and report."
:connection :ai
:available-steps [:files]
:memory {:table memory-ref
:auto-summarize true}
:max-iterations 8})]
result)}
What the installer sees vs. what they don't
| Concern | Visible to installer | Notes |
|---|---|---|
Flow output (:content) | Yes | The product they consume |
Output persist (:output-persist) | Yes | Blob in their workspace |
| Memory table | No | Internal to the author's workspace |
| Cost ledger | No | Flow-scoped KV, author's budget |
| Trace blob | No | Author's debugging resource |
| Evaluation scores | No | Author's quality tracking |
| Plan/checkpoint state | No | Internal to memory table |
| Connection binding | Setup only | Installer configures once, never sees internal usage |
Agent flow installation checklist
- [ ] Connection requirements declared in
:requireswith the right:provided-by - [ ] Memory table created at runtime by the flow, not hardcoded to a workspace URI
- [ ]
:output-persistused for any result the installer should access - [ ]
:costbudget set to prevent runaway LLM spend across installations - [ ] Setup form (
:kind :form) declared for any installer-visible configuration - [ ] Per-run fields declared in
:invocations - [ ] Install surface copy (
--publish-description) explains what the agent does,
not how it works internally - [ ] Tested with
breyta flows run <slug> --waitbefore release
Minimal installable agent flow example
{:slug :code-reviewer
:name "Code Reviewer"
:description "AI-powered code review for your repository."
:tags ["code-review" "ai"]
:discover {:public true}
:concurrency {:type :singleton :on-new-version :supersede}
:interfaces {:manual [{:id :run-review
:label "Run Review"
:invocation :default
:enabled true}]}
:requires [{:slot :ai
:type :http-api
:label "LLM Provider"
:provided-by :author}
{:slot :github-api
:type :http-api
:label "GitHub API"
:auth {:type :api-key}
:base-url "https://api.github.com"}]
:invocations {:default
{:label "Review input"
:inputs [{:name :repo
:label "Repository (owner/repo)"
:type :text
:required true}
{:name :branch
:label "Branch"
:type :text
:required false}]}}
:flow
'(let [input (flow/input)
repo-tree (flow/step :files :resolve-repo
{:op :resolve-source
:source {:type :git
:repo (str "https://github.com/" (:repo input) ".git")
:ref (or (:branch input) "main")
:connection :github-api}})
memory-ref (flow/step :table :ensure-memory
{:op :schema
:persist {:type :table :name "reviewer-memory"}})
result (flow/step :agent :review
{:connection :ai
:objective (str "Review the code in " (:repo input) " for issues.")
:inputs {:repo-tree repo-tree}
:available-steps [:files]
:memory {:table memory-ref
:auto-summarize true}
:cost {:enabled true
:budget {:max-tokens-total 1000000
:window-hours 720}}
:output {:format :json}
:output-persist {:name "review-report"
:content-type "application/json"}
:max-iterations 6})]
result)}
In this example:
- The author provides the LLM connection (installer never sees an API key)
- The installer provides the source-control connection and enters the repo/branch
- Memory is created at runtime (no hardcoded URI)
- Cost budget caps total LLM spend across all installations
- Output is auto-persisted as a JSON blob the installer can access
- The flow is installable via Discover with a manual interface
Connection item selects
Use a connection item select when an installer should choose something that
belongs to one of their connected systems: a repository, channel, project,
account, folder, customer record, or similar non-secret object.
The flow author declares the connection slot and the item type. The platform
uses cached non-secret metadata from the installer-bound connection to render
the dropdown. The submitted run input is still a normal scalar value, such as an
id, slug, or path.
The dropdown does not call the connected provider while the setup or run page is
rendering or saving. Provider-specific connection setup or refresh code
populates a connection-owned item cache first. Setup/run pages then read that
cache through the Breyta connection store and API. Submitted setup and run
values are validated against the same cache, and missing connections, empty
caches, disabled items, or unknown values fail closed with actionable setup/run
errors. This keeps option fetching bounded, auditable, and independent of flow
author code.
This fail-closed behavior applies only to fields that explicitly declare a
connection-item option source. It is separate from general connection health
checks, which remain best-effort readiness signals.
Generic shape:
{:key :target
:label "Target"
:field-type :select
:required true
:options-source {:type :connection-items
:slot :work-system
:item-type "project"
:value-field "id"
:label-field "name"}}
Use :slot for the local connection requirement that backs the dropdown.
:connection is accepted as a compatibility alias for the same local slot, but
authors should not set both unless the values match. :item-type is required
and can be provider-qualified, such as "github/repository" or
"slack/channel".
value-field, label-field, and description-field map cached item payload
fields into UI options. If those are omitted, the renderer falls back to common
fields: value or id for the submitted value, label for display text, and
description for helper text.
Typical item types are connector-defined. Examples include:
| Connected system | Example :item-type | Submitted value |
|---|---|---|
| Source control | "repository" or "github/repository" | owner/repo or repository id |
| Chat | "channel" or "slack/channel" | channel id |
| Issue tracker | "project" or "linear/project" | project id |
| CRM | "account" or "crm/account" | account id |
| Storage | "folder" or "drive/folder" | folder id |
For the code-reviewer example above, the repository text field can become a
connection item select backed by the source-control connection:
{:key :repo
:label "Repository"
:field-type :select
:required true
:options-source {:type :connection-items
:slot :github-api
:item-type "github/repository"
:value-field "full-name"
:label-field "full-name"}}
The submitted run input remains the selected owner/repo string. The platform
owns option refresh and picker UX; the flow should still fail clearly if the
connection loses access before source import.
Operators can inspect the cached item types and rows behind a connection with:
breyta connections items <connection-id>
breyta connections items <connection-id> --item-type github/repository --limit 25
breyta connections items <connection-id> --item-type github/repository --raw
The default output summarizes cached rows without raw provider payloads. Use
--raw only when you need to debug provider metadata.