# Breyta Flows Full Documentation Breyta Flows is a workflow and agent orchestration system for building, running, releasing, and publishing reliable AI-agent workflows. Concise index: https://flows.breyta.ai/llms.txt ## Agent Rules - Prefer EDN flow examples when showing Breyta flow definitions. - Use deterministic orchestration for workflow structure and reserve agents for bounded tasks. - Do not hardcode credentials; use Breyta connections, secrets, setup inputs, and runtime inputs. - When editing flows, validate drafts, inspect draft/live differences, use current entrypoint docs, and release explicitly before publishing. - Use Runtime Data Shapes when reading run results, resources, CLI output, or API responses. - Use documented Breyta CLI commands and flags; do not invent subcommands or options. - For LLM work, prefer structured prompts, explicit inputs, expected outputs, and model/provider settings from the docs. - For agent steps, keep tasks bounded with clear instructions, tool scope, success criteria, and handoff outputs. - For Breyta workspace access, use the :breyta step with explicit allowed operations and minimal required scope. - For public or installable flows, include install/smoke-test considerations when changes affect setup, inputs, outputs, pricing, or publishing. --- Document: Start Here URL: https://flows.breyta.ai/docs/start-here.md HTML: https://flows.breyta.ai/docs/start-here Last updated: 2026-05-21T12:24:31+02:00 # Start Here Breyta quickstart for the default flow lifecycle: draft working copy -> configure -> run. ## Quick Answer Use this sequence: ```bash # Optional for agent sessions; also follow any CLI missing/stale skill warning: breyta skills status --provider all breyta docs find "flows" --limit 5 --format json breyta flows search "" --limit 5 breyta flows templates search "" --limit 5 breyta resources search "" --limit 5 # Reuse/create/test connections up front (recommended when flow uses external systems): # breyta connections list # breyta connections create ... # breyta connections test # health/config check only; still do a real flow run breyta flows pull --out ./tmp/flows/.clj breyta flows lint --file ./tmp/flows/.clj breyta flows push --file ./tmp/flows/.clj # Configure/check before first run when the flow has :requires slots. # Example: # breyta flows configure --set api.conn=conn-... breyta flows configure check # Optional read-only verification (useful for CI/troubleshooting): breyta flows validate breyta flows run --input '{"name":"Ada"}' --wait # Open the run Output page returned by the CLI and confirm the final artifact is readable. ``` If you need the full command catalog or a broader docs map instead of the default lifecycle, jump to [CLI Commands](/docs/reference-cli-commands) or [Reference Index](/docs/reference-index). For task-oriented agent work, start with [Author Flows](/docs/playbook-author-flows), then open only the exact reference needed. ## Use Breyta Docs With AI Coding Agents Use the agent-readable docs index when working in Cursor, Claude Code, Codex, or another coding agent: - Breyta Flows docs index: https://flows.breyta.ai/llms.txt - Complete Breyta Flows docs: https://flows.breyta.ai/llms-full.txt - Markdown page form: append `.md` to any docs URL, for example https://flows.breyta.ai/docs/start-here.md - Context7 claim file: https://flows.breyta.ai/docs/context7.json - Context7 source: submit https://flows.breyta.ai/llms.txt or crawl https://flows.breyta.ai/docs Recommended agent rule: when creating or editing Breyta flows, read the Breyta Flows docs first and prefer the referenced playbooks and step references over stale examples. ## Flow Definition Mental Model A Breyta flow file is a DSL with Clojure/EDN syntax, not a normal Clojure program. Think of it as an orchestration definition: declare setup contracts, run inputs, interfaces, reusable templates/functions/steps/agents, then compose bounded `flow/step` calls in `:flow`. Put side effects and large data at step boundaries, keep orchestration deterministic, persist large results as resource refs, and use small map-oriented functions for transforms. ## Draft Vs Live `draft` is the staging/current workspace authoring state. Use it for edits, configure checks, and safe smoke tests. `live` is the released/runtime state. A flow is unreleased until a version is released or activated and the live path is verified. Say `draft verified` when only draft was exercised; reserve `released`, `end-user ready`, and `public ready` for live/install-shaped proof. ## Configure Before First Run (When Required) `breyta flows search "" --limit 5` searches actual workspace flow metadata. Use `breyta flows grep "" --limit 5` for workspace source/config search, such as tool names or upload fields. Approved reusable templates live under `breyta flows templates search "" --limit 5` and `breyta flows templates grep ""`. Add `--surface definition` or `--surface tools` when a literal appears in too many places. Treat search like `rg`: read the compact hit ref, matched surface/field, snippet or preview, and `nextCommand`; open only the one focused docs page, flow, template, or resource that the hit points to. If the new flow should build on existing reports, uploads, table resources, or prior run output, search resources before creating a new ingestion path: - `breyta resources search "" --limit 5` - `breyta resources read --limit 5` If your flow declares `:requires` slots or setup form fields, configure those values before the first run. Values that should be entered on each run belong in `:invocations` and are supplied with `breyta flows run --input ...`. New flows should expose manual runs through `:interfaces :manual` over an `:invocations` contract. `breyta flows run --input ... --wait` can use the enabled manual interface automatically. Declare at most one manual interface; when authors need multiple entry modes, model that choice as an invocation input such as `mode` or use another interface family. Use `--interface-id ` only when you need to select the declared manual interface explicitly. New flow source also needs an explicit `:concurrency` policy. Use [Flow Configuration](/docs/guide-flow-configuration) for the exact configuration workflow. ## How Requires, Configure, And Connections Fit Together | Stage | What it controls | Canonical command | |---|---|---| | Declare setup contract | `:requires` in the flow file defines required slots/setup inputs. | `breyta flows pull/push ...` | | Declare run contract | `:invocations` in the flow file defines per-run inputs. | `breyta flows pull/push ...` | | Bind values | Target bindings map each required slot to a real connection/secret/input value for a target. | `breyta flows configure --set .conn=` | | Verify readiness | Checks missing/invalid bindings and required inputs before runtime. For live, prefer version-pinned checks. | `breyta flows configure check ` or `breyta flows configure check --target live --version latest` | | Run | Runtime resolves the selected target bindings and executes. | `breyta flows run ...` | | Maintain wiring | Usage reports show where bindings reference a connection/secret. | `breyta connections usages ...` / `breyta secrets usages ...` | Connection/secret delete guards are binding-based. Even if a flow definition changed, delete remains blocked while any `draft`, `live`, or installation target still references that id. ## Draft Vs Live (Target Split) If CLI works but UI fails, first check whether the CLI tested draft while the web setup page, run form fields, installed flow, or public/end-user run used an old live version. `flows push` updates draft only; the UI install path follows the released live/install target. | Target | What it means | Commands | |---|---|---| | `draft` (default) | Your draft setup/config target (latest `flows push` + `flows configure`). Fast iteration lane for authoring and validation. `flows run` still executes the active version unless `--version` overrides it. | `breyta flows run/show/pull/validate ` | | `live` | Installed released target for stable/runtime consumption. Changed by `release`/`promote`, not by `push`. | `breyta flows run --target live` | ## Run Target After Release `breyta flows release --release-note-file ./release-note.md` creates a release, attaches a markdown release note, and updates the live installation target for the selected workspace by default. `breyta flows run ` still uses draft-target bindings/config unless you explicitly target live/install target. Brand-new flows with no active version are not runnable through `flows run` until you deploy or release a version. If the new flow has `:requires` slots, prepare the live target before the first release: - `breyta flows configure --target live --version latest --set .conn=` - `breyta flows configure check --target live --version latest` To run the installed live target, use one of: - `breyta flows run --target live --wait` - `breyta flows run --installation-id --wait` If you intentionally skipped end-user installation promotion (`--skip-promote-installations`), promote them later with: - `breyta flows promote ` - `breyta flows versions update --version --release-note-file ./release-note.md` if you need to edit the release note later Before releasing, inspect draft-vs-live changes with: - `breyta flows diff ` ## What You Get | Outcome | Why it matters | |---|---| | Config target checked | Catches missing bindings/secrets/inputs before first run. | | Optional explicit validation | Gives a read-only checkpoint for CI/troubleshooting. | | Required config applied | Prevents first-run failures from missing bindings/secrets/inputs. | | Runnable immediately after push | Keeps iteration fast in the same workspace. | | Verified run output | Confirms end-to-end behavior quickly. For human-facing flows, the Output page should be a readable artifact, usually a Markdown report. | ## Shape The Final Output The value returned by `:flow` is the run output. Treat that final return value as a product surface, not just an automation payload. For most human-facing flows, return a Markdown viewer envelope; when the report needs real Breyta tables, charts, files, images, videos, or JSON resources, embed them with `breyta-resource` fences. Use [Output Artifacts](/docs/guide-output-artifacts) before releasing any flow where a person will inspect the result. ## Next Playbooks And References | Need | Page | |---|---| | Create or edit a flow | [Author Flows](/docs/playbook-author-flows) | | Debug a failed run or verify output | [Debug And Verify](/docs/playbook-debug-and-verify) | | Release, promote, or install | [Release And Install](/docs/playbook-release-and-install) | | Public, Discover, or marketplace work | [Public And Marketplace](/docs/playbook-public-and-marketplace) | | Fanout, paging, concurrency, large artifacts | [Advanced Reliability](/docs/playbook-advanced-reliability) | | Full command catalog | [CLI Commands](/docs/reference-cli-commands) | | Reference map | [Reference Index](/docs/reference-index) | | Runtime data shapes and resource refs | [Runtime Data Shapes](/docs/reference-runtime-data-shapes) | | Final output viewers and Markdown resource embeds | [Output Artifacts](/docs/guide-output-artifacts) | | Setup reusable connections first | [Connections First](/docs/guide-connections-first) | | Publish a paid public flow | [Paid Public Flows](/docs/guide-paid-public-flows) | | Installation scopes (advanced) | [Installations](/docs/guide-installations) | | Requires/bindings/secrets | [Flow Configuration](/docs/guide-flow-configuration) | | Failures and triage | [Troubleshooting](/docs/guide-troubleshooting) | ## Notes - Canonical default surface is `flows pull`, `flows push`, `flows configure`, `flows configure check`, and `flows run`. - `flows diff` shows draft-vs-live or version-vs-version source changes on demand. - `flows release` (auto live install for selected workspace) and `flows promote` are rollout/governance operations. - `flows push` updates `draft` only; it does not change `live`. - `connections test` confirms health/config state, not end-to-end step execution. - Activation inputs are preserved across `flows release`/`flows promote`; update them explicitly via `flows configure` or `flows installations configure`. - Legacy command aliases still execute for compatibility but are intentionally hidden from default help. --- Document: Set Up A VPS Or VM For Breyta SSH URL: https://flows.breyta.ai/docs/guide-set-up-vps-vm-for-ssh.md HTML: https://flows.breyta.ai/docs/guide-set-up-vps-vm-for-ssh Last updated: 2026-05-10T21:06:10+02:00 # Set Up A VPS Or VM For Breyta SSH Use this guide to provision a Linux VM/VPS, prepare SSH auth and host verification, create a Breyta SSH connection, and smoke test the `:ssh` step before moving to long-running remote agents. ## Quick Answer If you want Breyta to run commands on your own server, you need: - a Linux VM/VPS with a public hostname or IP - inbound SSH access on port `22` (or your chosen SSH port) - a Linux user that Breyta can log in as - SSH auth, preferably a private key stored as a Breyta secret - a `known_hosts` entry stored as a Breyta secret - a flow/template that declares an SSH connection slot and the secret refs you want to use For short commands, use a single sync `:ssh` step. For long-running jobs, use `:ssh` kickoff plus `:wait` callback. ## Choose The Right Execution Model | Use case | Pattern | Why | | --- | --- | --- | | Run a short remote command | Sync `:ssh` | The flow can wait for command exit naturally. | | Start an agent, worker, scraper, or code job that can run for minutes or hours | `:ssh` kickoff + `:wait` callback | More reliable than keeping one SSH activity open the whole time. | | Unsure which one you need | Start with sync only if the command is short and chatty | Silent or long-lived jobs should move to the async pattern early. | Use [Step SSH](/docs/reference-step-ssh) for schema details and [Remote Agents (SSH)](/docs/guide-remote-agents-ssh) for the long-running pattern. ## 1. Provision A Linux Host Breyta works best with a standard Linux VM/VPS. Ubuntu and Debian are the easiest starting point. Minimum practical requirements: | Requirement | Why it matters | | --- | --- | | Public DNS name or public IP | Production-like Breyta environments block private-IP SSH targets by default. | | Inbound SSH access | Breyta needs to reach the host over SSH. | | Outbound HTTPS access | Remote-agent flows need to POST results back to Breyta callbacks. | | A stable Linux user | Your SSH connection config must specify a `username`. | | `sh` on the remote host | The SSH step runs commands through `sh -lc`. | If you are using a cloud provider, reserve a static IP or DNS name when possible. It makes `known_hosts` and future rotation much easier. ### What To Buy For a first Breyta SSH setup, look for: - a self-managed VPS or VM, not shared hosting - Ubuntu 24.04 LTS or Debian 12 - a public IPv4 address - a provider option to inject your SSH public key during provisioning - snapshots or backups - a region close to your users or data A small starting machine is usually enough for smoke tests and simple automation: - `1-2 vCPU` - `2-4 GB RAM` - `40+ GB SSD` Increase size when your remote workload actually needs it, for example code indexing, browser automation, or larger AI/tooling jobs. ### Popular Starting Points These are common places to start if you do not already have a preferred provider: - [Hetzner Cloud](https://www.hetzner.com/cloud) for straightforward self-managed cloud servers. Hetzner also publishes a direct [create a server guide](https://docs.hetzner.com/cloud/servers/getting-started/creating-a-server/). - [Hostinger VPS Hosting](https://www.hostinger.com/vps-hosting) if you want a more consumer-friendly VPS product with Linux templates and an account dashboard. - [Google Cloud Compute Engine](https://cloud.google.com/products/compute) if you already use Google Cloud or want deeper cloud-networking and IAM controls. Google also documents how to [create and start a Compute Engine instance](https://cloud.google.com/compute/docs/instances/create-start-instance). No matter which provider you choose, try to make the same choices during provisioning: - pick a plain Linux image rather than an app-specific template unless you know you need it - add your SSH public key at creation time if the provider supports it - enable backups or snapshots - keep the first machine simple - confirm that the machine will have a public IP or public DNS name ## 2. Create A Linux User For Breyta Work You can use an existing user like `ubuntu`, but a dedicated user is usually cleaner. Example on the VM as an admin: ```bash sudo adduser breyta sudo usermod -aG sudo breyta ``` Then install your public key for that user: ```bash sudo install -d -m 700 -o breyta -g breyta /home/breyta/.ssh sudo tee /home/breyta/.ssh/authorized_keys >/dev/null <<'EOF' ssh-ed25519 AAAA... your-key-comment EOF sudo chown breyta:breyta /home/breyta/.ssh/authorized_keys sudo chmod 600 /home/breyta/.ssh/authorized_keys ``` If you do not have a key yet, create one locally: ```bash ssh-keygen -t ed25519 -f ~/.ssh/breyta_vm -C "breyta-ssh" ``` ## 3. Allow SSH Inbound Safely Open your SSH port in the provider firewall or security group. Prefer one of these: - only allow your office/VPN egress IPs - use a bastion or VPN - use your cloud provider's SSH forwarding/IAP features Avoid leaving port `22` open to `0.0.0.0/0` unless you have to. If you use a non-default SSH port, use that same port consistently in the SSH check, `ssh-keyscan`, and the Breyta connection config below. ## 4. Verify SSH Before Involving Breyta Confirm that normal SSH works from your own machine first: ```bash ssh -i ~/.ssh/breyta_vm breyta@ 'uname -a' ``` Do not continue until this works. Breyta will fail with the same underlying network or auth problems. Use the same private key file here that you plan to store in Breyta. Do not rely on whichever key your local SSH agent happens to pick automatically. ## 5. Capture The Host Key For `known_hosts` The `:ssh` step expects host key verification unless you explicitly disable it for development. Create a `known_hosts` file locally: ```bash mkdir -p ./tmp ssh-keyscan -H -p 22 > ./tmp/ssh-known-hosts ``` Then verify that the fingerprint you captured matches the host you actually provisioned. A simple check is: ```bash ssh-keygen -lf ./tmp/ssh-known-hosts ``` and on the VM: ```bash sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub ``` Proceed only when the fingerprints match, or when you have matched the fingerprint against the value shown by your VPS/cloud provider. Recommended: - use the final DNS name if you have one - regenerate this file if you replace the VM or rotate its host keys Dev-only escape hatch: - set `"insecure-skip-host-key-verification": true` on the SSH connection config - do this only for temporary development environments ## 6. Prepare The VM Runtime For simple sync SSH commands, the VM only needs the command-line tools your command uses. For long-running remote-agent flows, prepare the VM like an actual worker machine: - install the CLIs and runtimes the job needs - make sure repository access and auth are already working if the job touches code - make sure the VM can reach Breyta callback URLs over HTTPS - use `nohup`, `tmux`, `systemd`, or another background-process strategy Example: if your Breyta flow starts a long-running code or agent job on the VM, the machine may also need: - `git` - `curl` - the language runtime your task uses - any provider CLI or SDK the task depends on - repository auth if it clones private code - outbound access to `https://flows.breyta.ai` so it can send a callback when the work is done ## 7. Make Sure The Flow Or Template Exposes SSH Slots The connection itself points at secret refs, but the secret values still need to be stored in the workspace through flow configuration. That means your flow or copied template should declare: - one SSH connection slot, often `:vps` - one secret slot for the private key - one secret slot for `known_hosts` - optionally one secret slot for a key passphrase Example: ```clojure {:requires [{:slot :vps :type :ssh :label "VPS (SSH)"} {:slot :ssh-private-key :type :secret :secret-ref :ssh-private-key :label "SSH private key"} {:slot :ssh-known-hosts :type :secret :secret-ref :ssh-known-hosts :label "SSH known_hosts"} {:slot :ssh-key-passphrase :type :secret :secret-ref :ssh-key-passphrase :label "SSH key passphrase" :optional true}]} ``` This is a common pattern for SSH-backed templates because the connection can reference stable secret ids while the actual secret values stay out of the flow definition. ## 8. Store The SSH Secrets Through Flow Configuration The command examples below assume your `breyta` CLI is already configured with default API, workspace, and token settings. Use `@/absolute/path` or `@./relative/path` to read multiline file content into a secret value. Do not use `@~/.ssh/...`, because shells do not expand `~` reliably in that form. Assuming your flow exposes the secret slots above: ```bash breyta \ flows configure \ --set ssh-private-key.secret=@$HOME/.ssh/breyta_vm \ --set ssh-known-hosts.secret=@./tmp/ssh-known-hosts ``` If the key has a passphrase: ```bash breyta \ flows configure \ --set ssh-key-passphrase.secret=@./tmp/ssh-key-passphrase.txt ``` Use [Flow Configuration](/docs/guide-flow-configuration) and [Secrets](/docs/guide-secrets) for the full model. ## 9. Create The SSH Connection In Breyta Create a workspace connection that points at the host and references the stored secrets: ```bash breyta \ connections create \ --type ssh \ --backend ssh \ --name "My VPS (SSH)" \ --config '{ "host":"", "port":22, "username":"breyta", "auth":{ "type":"private-key", "secret-ref":"ssh-private-key", "passphrase-secret-ref":"ssh-key-passphrase" }, "known-hosts":{"secret-ref":"ssh-known-hosts"} }' ``` Copy the returned connection id. Optional verification: ```bash breyta \ connections test ``` This confirms that Breyta sees the connection as configured and active. It is useful, but it is not a substitute for a real SSH-backed flow run. ## 10. Bind The Connection To The Flow Bind the SSH connection to the flow's SSH slot and check configuration: ```bash breyta \ flows configure \ --set vps.conn= ``` ```bash breyta \ flows configure check ``` If the flow uses a different slot name than `vps`, bind that slot instead. If this is a brand-new flow with no active version yet, also prepare the live target now so the first release can succeed: ```bash breyta \ flows configure \ --target live \ --version latest \ --set vps.conn= ``` ## 11. Smoke Test With A Safe Command Do one short, boring command before you trust the VM for a real template flow: - `echo hello` - `uname -a` - `pwd` If your flow accepts a `command` input, the run may look like this: ```bash breyta \ flows run --wait \ --input '{"command":"uname -a"}' ``` If your copied template does not accept a freeform `command` input, run its normal manual interface with the smallest safe input it supports. If `flows run` returns `no_active_version`, the flow has not been released yet. Create the first release, then rerun the smoke test: ```bash breyta flows release ``` For flows with `:requires` slots, that first release will only succeed if the live target has already been configured for `--version latest`, as shown above. ## 12. Move Long-Running Jobs To The Callback Pattern If the remote job can take several minutes or more, do not keep one sync SSH step open for the full run. Use this pattern instead: 1. Breyta uses `:ssh` to start the remote worker quickly. 2. The remote worker keeps running on the VM. 3. Breyta pauses in a `:wait` step. 4. The remote worker POSTs JSON back to Breyta when it finishes. Your VM must be able to reach a callback URL shaped like: ```text https://flows.breyta.ai//events/ ``` Local check from the VM: ```bash curl -I https://flows.breyta.ai ``` Use [Remote Agents (SSH)](/docs/guide-remote-agents-ssh) for the step-level authoring pattern. ## Troubleshooting | Symptom | Likely cause | Fix | | --- | --- | --- | | SSH connect timeout | Firewall, wrong host, wrong port | Re-test plain `ssh` from your machine first. | | `Permission denied (publickey)` | Wrong user or wrong key | Verify the Linux username and `authorized_keys`. | | `Missing SSH host key verification config` | No `known_hosts` secret configured | Store `known_hosts` and reference it from the connection. | | `Secret not found` | The flow never stored the referenced secret | Configure the secret slot on the flow before creating the connection. | | `SSH host resolves to private IP` | Host is not publicly reachable for your Breyta environment | Use a public host or an environment configured for private IP SSH. | | The SSH step exits but the remote job is still running | You modeled a long-lived process as sync SSH | Switch to `:ssh` kickoff + `:wait` callback. | ## Related - [Step SSH](/docs/reference-step-ssh) - [SSH Testing](/docs/guide-ssh-testing) - [Remote Agents (SSH)](/docs/guide-remote-agents-ssh) - [Flow Configuration](/docs/guide-flow-configuration) - [Secrets](/docs/guide-secrets) --- Document: Flow Basics URL: https://flows.breyta.ai/docs/build-flow-basics.md HTML: https://flows.breyta.ai/docs/build-flow-basics Last updated: 2026-05-10T21:06:10+02:00 # Flow Basics Breyta flow basics for authoring deterministic workflow automation with `:slug`, `:concurrency`, `:invocations`, `:interfaces`, and `:flow`. ## Quick Answer Start with a minimal deterministic flow, confirm it runs, then add one external requirement and one step at a time. Keep `:flow` focused on orchestration, label branch nodes with metadata, and use top-level `:functions` for shaping logic. ## Do This Now Use this minimal structure as your base: ```clojure {:slug :hello-world :name "Hello World" :concurrency {:type :singleton :on-new-version :supersede} :functions [{:id :build-response :language :clojure :code "(fn [input] {:message (str \"Hello, \" (:name input) \"!\") :input input})"}] :invocations {:default {:inputs [{:name :name :type :text :label "Name" :default "World"}]}} :interfaces {:manual [{:id :run :label "Run" :invocation :default :enabled true}]} :flow '(let [input (flow/input) greeting-input ^{:label "Greeting input" :yes "Use provided name" :no "Default to World"} (if (:name input) input (assoc input :name "World")) output (flow/step :function :build-output {:ref :build-response :input greeting-input})] output)} ``` ## What This Does - defines a unique flow identity - enables manual execution through a manual interface - returns deterministic output - models orchestration in `:flow` and shaping in top-level `:functions` Field intent: - `:slug`: stable machine id for CLI/API operations - `:name`: human-readable label - `:concurrency`: controls overlap/version handoff behavior - `:interfaces`: run/call entry points. For manual interfaces, `:label` also controls the user-facing run CTA copy in Resource UI. - `:flow`: deterministic orchestration body ## When To Use Use this before adding external systems or advanced step chains. ## First Extension Path After this runs successfully: 1. add `:requires` for an external system 2. add one `flow/step` consuming that requirement 3. keep output shape simple and explicit 4. push -> validate -> configure (if required) -> run before adding more steps This keeps failures easy to isolate and fix. ## Draft Vs Live In Practice - Use `draft` (default) while building: `push`, `validate`, `configure`, then `run`. - `push` affects `draft` only (latest working copy), not installed `live`. - Use `live` when you need to verify what installed consumers run: `breyta flows run --target live --wait`. - `breyta flows release ` publishes a release and updates live for the selected workspace by default. ## Go Deeper - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [Run Concurrency](/docs/reference-run-concurrency) - [Flow Features And Config Toggles](/docs/guide-flow-features-and-config-toggles) --- Document: Flow Authoring URL: https://flows.breyta.ai/docs/build-flow-authoring.md HTML: https://flows.breyta.ai/docs/build-flow-authoring Last updated: 2026-05-20T03:00:25+02:00 # Flow Authoring Breyta flow authoring guide for production workflows: structure definitions, publish safely, configure runtime requirements, and promote releases. ## Quick Answer Author in a strict sequence: define shape, push updates, configure + check runtime requirements, optionally validate, release an immutable version, then run. Keep orchestration thin in `:flow` and move shaping logic/content into `:functions` and `:templates` as complexity grows. ## Do This Now Design your flow in this order: 1. define `:slug` and `:name` 2. set `:concurrency` 3. declare setup in `:requires` 4. declare per-run input in `:invocations` 5. expose manual/client ingress in `:interfaces` and time automation in `:schedules` 6. define the final output contract 7. implement `:flow` with deterministic logic For public/end-user flows, define the output contract as a human presentation surface before release. Default to one readable Markdown artifact; use a curated table or dedicated media viewer only when that viewer is clearly better for the user. When a Markdown report needs real Breyta tables, charts, downloads, images, video, nested Markdown, or JSON resources, embed them with fenced `breyta-resource` blocks so they render in the document flow. Keep raw structured/debug payloads out of the final public result unless they are explicitly the intended output. ## What This Does - keeps flow behavior predictable - separates runtime bindings from authoring - reduces replay and debugging issues For the exact command lifecycle (push/validate/release/configure/run), use [CLI Workflow](/docs/guide-cli-workflow). ## When To Use Use this once your first flow works and you need production-ready structure. ## Advanced Options - keyed concurrency - multi-interface and scheduled flows - reusable requirements/bindings - templates for prompts, request bodies, and SQL - function refs for reusable deterministic transforms - `:persist` refs for data-heavy step outputs: inline results should stay under 512 KB, 1 MB is the hard step/DB/code/notify payload cap, and persisted blob writes cap at 50 MB retained or 4 GB ephemeral - `:metering` on steps with known API/tool costs, so creator-facing run cost estimates have readable line items - child-flow orchestration with `flow/call-flow` for complex branches - bounded child-workflow batch spawn/collect with `:fanout` when every item is `{:type :call-flow ...}` ## Production Pattern: Parent + Child Flows When a single `:flow` starts mixing ingress logic, routing, and domain application behavior, split it: 1. parent flow does ingress + normalization 2. parent delegates specialized branches with `flow/call-flow` 3. parent uses `:fanout` only for bounded sibling child-workflow batches when spawn/collect semantics are needed 4. each child flow owns one domain concern (routing, apply/update, reporting) This keeps orchestration readable and reduces duplicate branching logic across flows. ## Common Anti-Patterns - adding bindings commands before push succeeds - hiding side effects in non-step code - changing many steps at once without isolation checks - using unclear step ids that make timelines hard to read - returning internal tables, `res://` refs, or raw debug maps as public manual-run output - pasting EDN/JSON maps into Markdown paragraphs instead of using a raw viewer, JSON resource embed, or fenced code block ## Go Deeper - [CLI Workflow](/docs/guide-cli-workflow) - [Flow Configuration](/docs/guide-flow-configuration) - [Output Artifacts](/docs/guide-output-artifacts) - [Templates](/docs/reference-templates) - [Functions](/docs/reference-functions) - [Runs And Outputs](/docs/operate-runs-and-outputs) - [Run Cost Estimates](/docs/guide-run-cost-estimates) - [Flow Definition](/docs/reference-flow-definition) - [Run Concurrency](/docs/reference-run-concurrency) - [Flow Features And Config Toggles](/docs/guide-flow-features-and-config-toggles) --- Document: Flow Naming URL: https://flows.breyta.ai/docs/build-flow-naming.md HTML: https://flows.breyta.ai/docs/build-flow-naming Last updated: 2026-03-10T16:44:12+01:00 # Flow Naming ## Quick Answer Name public and reusable Breyta flows like pages that should satisfy a real query, not like internal implementation artifacts. Good public flow naming should make a human think: - "Yes, this is what I was looking for" And make search systems infer: - "This page directly answers the query and is about one clear use case" ## When this matters most Use this guidance for flows that are: - approved for reuse - user-facing - published to dedicated landing pages - likely to be discovered through Google, Perplexity, ChatGPT, Gemini, or other AI-search tools For internal-only flows, operator readability still matters, but SEO/AEO pressure is lower. ## Core rule Do not name public flows like internal objects. Prefer: - clear outcome - clear workflow, automation, or agent intent - clear integration or system when it narrows the use case Avoid: - repo names unless they are part of actual search intent - implementation-heavy wording - vague labels like "processor", "handler", or "pipeline" when users search for "workflow", "automation", or "agent" ## Optimize for query shapes, not formulas Do not force a rigid pattern like: - `Outcome workflow for integration` That is a useful drafting heuristic, not the final rule. Real searches are usually shaped more like: - `gmail ai support agent` - `github pull request automation` - `google drive folder sync workflow` - `meeting transcription workflow with assemblyai` - `stripe monthly mrr reporting automation` - `how to automate customer support emails in gmail` Your public naming should sound like a plausible search result, not a taxonomy label. ## Choose one primary intent Each public reusable flow should target one primary intent. Typical intent buckets: - `workflow` - `automation` - `agent` - `template` - `integration` Examples: - use `agent` when the flow behaves autonomously over time - use `workflow` when it is a repeatable process with multiple steps - use `automation` when the user mainly cares about the outcome being automated - use `template` when the searcher wants something reusable and ready-made - use `integration` when the cross-tool connection is the main reason to visit Do not mix multiple primary intents in one title unless it reads naturally. ## Field-by-field guidance ### `:name` Use a clear, rankable title. Preferred ingredients: - outcome first - workflow, automation, or agent keyword when it matches intent - core integration or tool when it narrows the use case Examples: - `AI Support Agent for Gmail` - `GitHub Pull Request Automation with OpenAI and SSH` - `Google Drive Folder Sync Workflow` - `Meeting Transcription Workflow with AssemblyAI` - `Stripe Monthly MRR Reporting Automation` Avoid: - internal repo names - version suffixes unless needed - titles that only make sense to Breyta teammates ### slug Treat the slug as a search-facing URL, not an internal identifier. Rules: - use lowercase hyphenated plain English - keep it short but descriptive - reflect the same primary intent as the title - avoid weak filler words Good: - `ai-support-agent-gmail` - `github-pull-request-automation-openai-ssh` - `google-drive-folder-sync-workflow` - `meeting-transcription-workflow-assemblyai` Weaker: - `support-agent-llm` - `flows-api-codex-pr-agent` - `meet-assemblyai-transcribe` ### `:description` Use one sentence, occasionally two. It should answer: - what triggers it - what systems it uses - what outcome it produces Good: - `Automatically monitors a Gmail inbox, drafts AI replies, sends responses, and keeps support email moving without manual triage.` - `Runs Codex over SSH against a repository, applies code improvements, and creates a GitHub pull request with the result.` Avoid: - `processes data` - `handles workflow logic` - `syncs things` - wording that describes mechanics but not user outcome ### tags Tags should help discovery, reuse, and page clustering. Include 4-8 terms from these buckets: - outcome - audience or use case - integration or tool - modality Examples: - `support`, `gmail`, `ai-agent`, `email-automation`, `customer-support` - `github`, `pull-request`, `code-automation`, `openai`, `ssh` Avoid: - only internal state tags like `draft` - generic words with no discovery value ## Step titles for public flows Even if the landing page is mostly metadata-driven, visible step names still shape perceived quality. Use: - plain-language action titles - titles that reveal the job being done - verbs humans understand immediately Prefer: - `Check Gmail for support emails` - `Draft reply with AI` - `Create GitHub pull request` - `Start AssemblyAI transcription` Avoid: - `normalize input` - `hydrate state` - `finalize result` - `prepare payload` If the internal step id must remain technical, improve the visible `:title`. ## Human + AI-search test Before finalizing a public flow name, ask: 1. Would a human plausibly type this or click it? 2. Would the first paragraph of the page clearly satisfy that query? 3. Does the title sound like a result, not an internal object? 4. Does the slug reinforce the same intent? 5. Is the page targeting one strong long-tail query instead of several weak ones? If any answer is no, rewrite. ## Rewrite order When renaming a reusable or public flow: 1. Pick the primary query shape. 2. Rewrite `:name` to match that shape. 3. Rewrite the slug if the current slug is internal or weak. 4. Rewrite `:description` around trigger + systems + outcome. 5. Rewrite tags around outcome, use-case, and tool terms. 6. Rewrite visible step titles to plain-language actions. ## Quick examples Weak: - `support-agent-llm` - `Autonomous AI Support Agent for Gmail` Stronger if targeting practical long-tail: - `AI Support Agent for Gmail` - slug: `ai-support-agent-gmail` Weak: - `flows-api-codex-pr-agent` - `Autonomous flows-api Clojure improvement agent over SSH` Stronger if targeting outcome over repo internals: - `GitHub Pull Request Automation with OpenAI and SSH` - slug: `github-pull-request-automation-openai-ssh` Weak: - `meet-assemblyai-transcribe` Stronger: - `Meeting Transcription Workflow with AssemblyAI` - slug: `meeting-transcription-workflow-assemblyai` ## Priority order when tradeoffs exist For public reusable flows, optimize in this order: 1. clarity of user outcome 2. fit to likely query wording 3. integration specificity 4. operator readability in UI 5. internal implementation fidelity If internal precision and public clarity conflict, prefer public clarity in visible metadata. ## Related - [Flow Basics](/docs/build-flow-basics) - [Flow Authoring](/docs/build-flow-authoring) - [Patterns (Do/Do Not)](/docs/guide-patterns-do-dont) - [Flow Definition](/docs/reference-flow-definition) --- Document: Runs And Outputs URL: https://flows.breyta.ai/docs/operate-runs-and-outputs.md HTML: https://flows.breyta.ai/docs/operate-runs-and-outputs Last updated: 2026-05-15T14:59:18+02:00 # Runs And Outputs Breyta operations guide for post-change smoke checks, output validation, and incident triage. ## Quick Answer After push/configure changes, run `breyta flows run --wait`, inspect final output shape, and confirm expected side effects. For public/end-user flows, also open the Output page and verify that the first artifact is the intended human-readable result. For output shaping rules, use [Output Artifacts](/docs/guide-output-artifacts). The normal rich result pattern is a Markdown report, optionally with `breyta-resource` fences for embedded tables, charts, downloads, media, nested Markdown, or JSON resources. ## Do This Now | Step | Action | |---|---| | 1 | Start a run for the flow target you just changed. | | 2 | Wait for terminal state and inspect run details. | | 3 | Validate output contract keys/types used by consumers. | | 4 | Confirm expected external side effects completed. | | 5 | For public/end-user flows, inspect the Output page for human readability. | ```bash breyta flows run --input '{"n":41}' --wait breyta runs show --pretty ``` To inspect creator-facing run cost estimate data, include all steps: ```bash breyta runs show --steps 0 --pretty ``` The run response includes aggregate and step-level cost estimates when the run has priced lines. Use [Run Cost Estimates](/docs/guide-run-cost-estimates) for `:metering`, token pricing, and breakdown fields. ## What This Confirms | Check | Signal | |---|---| | Current target is active | The expected runtime target is reachable. | | End-to-end execution path | Trigger-to-output path works in real runtime conditions. | | Output contract stability | Consumers receive expected keys/types. | | Integration health | External writes/actions happened as expected. | ## Public Flow Output Checks Public manual-run output should be readable without opening debug data or knowing the internal schema. | Check | Passes when | |---|---| | Primary artifact | The first viewer item is readable markdown by default, or a deliberate table/media viewer when that is better for the user. | | Table columns | Any separate table viewer uses human labels such as `Who`, `Followers`, `Contact`, `Link`, and `Why relevant`. | | No raw debug panel | There is no default `:raw` viewer or `Structured output` panel unless structured data is the product output. | | No internal refs | Top-level `res://` table refs, persisted run-table refs, and automation-only maps are not visible in the public result. | | No implementation fields | Internal fields such as `top_candidates_preview`, status counters, trace ids, and table ids are not visible to end users. | | Embedded resources | Markdown `breyta-resource` embeds render in place; users do not see raw fenced blocks or technical resource URIs. | | Structured detail | EDN/JSON maps appear as a raw viewer, JSON resource embed, or fenced code block, not as one-line paragraph text. | If a flow needs both public presentation and machine-readable automation data, persist the automation data as resources or tables and return only the curated presentation in the final public output. ## Incident Checklist | Step | Action | |---|---| | 1 | Identify failing step from run timeline. | | 2 | Verify required connections/secrets/inputs are configured for the runtime target. | | 3 | Isolate and reproduce failing step behavior. | | 4 | Decide retry, cancel, rollback, or compensating follow-up flow. | Inspect persisted artifacts with `breyta resources ...` by listing workflow resources and reading `res://` refs. ## Advanced/Release Validation For broader scenario coverage (failure matrix, CLI/API regression checks, release evidence), use your integration test suite and release validation checks. ## Go Deeper - [Troubleshooting](/docs/guide-troubleshooting) - [Output Artifacts](/docs/guide-output-artifacts) - [Run Cost Estimates](/docs/guide-run-cost-estimates) - [Persisted Results And Resource Refs](/docs/guide-persisted-results-and-resources) - [Waits, Signals, And Timeouts](/docs/guide-waits-signals-and-timeouts) - [CLI Commands](/docs/reference-cli-commands) --- Document: Run Cost Estimates URL: https://flows.breyta.ai/docs/guide-run-cost-estimates.md HTML: https://flows.breyta.ai/docs/guide-run-cost-estimates Last updated: 2026-05-08T08:41:28+02:00 # Run Cost Estimates Breyta run cost estimate guide for creator-facing average run cost, per-run breakdowns, fixed API costs, and LLM token pricing. ## Quick Answer Run cost estimates are advisory. Breyta records step-level cost estimates during a run, summarizes them on the flow page as `Avg Estimated Run Cost`, and exposes the same data through `breyta runs show`. Add costs in two places: | Cost type | Where to add it | Who owns it | |---|---|---| | Fixed API or tool request cost | `:metering` on the `flow/step` config | Flow author | | LLM token cost | `llm-pricing` in `breyta.flows.cost-estimates` | Breyta platform code | Use micros for money fields. One dollar is `1000000` micros, so `$0.0020` is `2000` micros. ## Fixed API Or Tool Costs Use `:metering` when a step has a known per-call cost that Breyta cannot infer from the provider response. ```clojure (flow/step :http :get-users {:connection :api :path "/users" :method :get :metering {:unit :request :unit-cost-micros 2000 :label "JSONPlaceholder request"}}) ``` `unit-cost-micros` is the cost per completed step execution. The estimate line uses `:label` in creator-facing breakdowns, so make it readable. Use this for: - paid API requests with a known per-request fee - enrichment providers with fixed lookup costs - internal tools where the author wants a transparent estimate line Do not use `:metering` for LLM token pricing. LLM costs come from provider/model usage. ## LLM Token Costs LLM and agent steps estimate token cost from provider/model usage returned by the step result. Rates live in `llm-pricing` in `breyta.flows.cost-estimates`. Rates are stored as micros per 1 million tokens: ```clojure {:openai {"gpt-5-mini" {:input-micros-per-mtok 250000 :cached-input-micros-per-mtok 25000 :output-micros-per-mtok 2000000}}} ``` This example means: | Rate | Micros | Dollars | |---|---:|---:| | Input tokens | `250000` | `$0.25 / 1M` | | Cached input tokens | `25000` | `$0.025 / 1M` | | Output tokens | `2000000` | `$2.00 / 1M` | If a model is not in the table, Breyta can still show usage but marks that estimate line as unpriced. ## View Run Cost Data The flow page shows the completed-run average as `Avg Estimated Run Cost`. Open the cost detail from the flow page to see the line-item breakdown. The CLI uses the same run API response: ```bash breyta runs show --steps 0 --pretty ``` Look for: - `data.run.costEstimate` for the aggregate run estimate - `data.run.costEstimate.lineItems` for the aggregate breakdown - `data.run.steps[].costEstimate` for per-step estimates Representative shape: ```json { "costEstimate": { "kind": "estimated", "currency": "USD", "amountMicros": 3500, "estimatedUsd": "$0.0035", "unknownLineCount": 0, "lineItems": [ { "source": "configured-metering", "label": "JSONPlaceholder request", "amountMicros": 2000, "estimatedUsd": "$0.0020", "unit": "request", "quantity": 1 }, { "source": "llm", "provider": "openai", "model": "gpt-5-mini", "amountMicros": 1500, "estimatedUsd": "$0.0015", "usage": { "inputTokens": 1000, "outputTokens": 100, "totalTokens": 1100 } } ] } } ``` ## Notes And Limits - Estimates are for creator visibility and pricing guidance. They are not a billing ledger. - A run estimate can mix priced and unpriced line items. When that happens, the known total is shown with an unknown-line count. - Average run cost is based on completed runs. - Step-level `:metering` is a flow definition option, so changing it requires pushing and releasing the flow version you want users to run. - Token rates are platform code. Keep the pricing version updated when the table changes. ## Go Deeper - [Runs And Outputs](/docs/operate-runs-and-outputs) - [Flow Authoring](/docs/build-flow-authoring) - [Step Reference](/docs/reference-step-reference) - [Step HTTP](/docs/reference-step-http) - [Step LLM](/docs/reference-step-llm) --- Document: Hello World URL: https://flows.breyta.ai/docs/example-hello-world.md HTML: https://flows.breyta.ai/docs/example-hello-world Last updated: 2026-05-10T21:06:10+02:00 # Hello World Breyta hello world example for validating first flow push and run behavior. ## Quick Answer Use this minimal flow to verify your CLI, environment variables, and end-to-end run path before adding integrations. Even here, keep shaping logic in top-level `:functions`, call it via `flow/step :function`, and label orchestration branches for readable run traces. ## Use Case Smallest possible flow to validate push/run wiring. ## Full Definition ```clojure {:slug :hello-world :name "Hello World" :concurrency {:type :singleton :on-new-version :supersede} :functions [{:id :build-greeting :language :clojure :code "(fn [input] {:message (str \"Hello, \" (:name input) \"!\") :input input})"}] :invocations {:default {:inputs [{:name :name :type :text :label "Name" :default "World"}]}} :interfaces {:manual [{:id :run :label "Run" :invocation :default :enabled true}]} :flow '(let [input (flow/input) greeting-input ^{:label "Greeting target" :yes "Use provided name" :no "Fallback to World"} (if (:name input) input (assoc input :name "World")) output (flow/step :function :build-output {:ref :build-greeting :input greeting-input})] {:breyta.viewer/kind :markdown :breyta.viewer/options {:title "Greeting"} :breyta.viewer/value (str "# Greeting\n\n" (:message output))})} ``` ## Run It ```bash breyta flows push --file ./tmp/flows/hello-world.clj breyta flows validate hello-world breyta flows run hello-world --wait ``` Optional input: ```bash breyta flows run hello-world --wait --input '{"name":"Ada"}' ``` ## Expected Output A Markdown output artifact with a short greeting. The intermediate function still returns structured data, but the final `:flow` return value is shaped as a human-readable viewer artifact. See [Output Artifacts](/docs/guide-output-artifacts) for the full final output viewer contract. ## Why This Matters This example validates the full authoring and runtime path with minimal noise. - no external bindings required - deterministic result shape - safe baseline for CLI/environment troubleshooting If this fails, fix environment or CLI workflow before building larger flows. Customize in this order: 1. input shape 2. output contract 3. `:requires` and external steps ## Try Next - [HTTP Enrichment](/docs/example-http-enrichment) - [Flow Basics](/docs/build-flow-basics) --- Document: HTTP Enrichment URL: https://flows.breyta.ai/docs/example-http-enrichment.md HTML: https://flows.breyta.ai/docs/example-http-enrichment Last updated: 2026-05-10T21:06:10+02:00 # HTTP Enrichment Breyta HTTP enrichment example for fetching external API data inside a flow step. ## Quick Answer Bind an HTTP connection in `:requires`, call it via `flow/step :http`, and return normalized payload fields. Keep normalization in top-level `:functions`, not inline in orchestration, and label branch logic with metadata. ## Use Case Fetch external data and return transformed output. ## Full Definition ```clojure {:slug :http-enrichment :name "HTTP Enrichment" :concurrency {:type :singleton :on-new-version :supersede} :requires [{:slot :public-api :type :http-api :label "Public API" :base-url "https://jsonplaceholder.typicode.com" :auth {:type :none}}] :functions [{:id :normalize-user :language :clojure :code "(fn [input] {:lookup-user-id (:user-id input) :user (:user input)})"}] :invocations {:default {:inputs [{:name :user-id :type :number :label "User ID" :default 1}]}} :interfaces {:manual [{:id :run :label "Run" :invocation :default :enabled true}]} :flow '(let [input (flow/input) user-id ^{:label "Resolve user id" :yes "Use provided user-id" :no "Fallback to default user"} (if (:user-id input) (:user-id input) 1) user (flow/step :http :fetch-user {:connection :public-api :path (str "/users/" user-id)}) normalized (flow/step :function :normalize-response {:ref :normalize-user :input {:user-id user-id :user user}})] normalized)} ``` ## Run It ```bash breyta flows push --file ./tmp/flows/http-enrichment.clj breyta flows validate http-enrichment breyta flows run http-enrichment --wait --input '{"user-id":2}' ``` ## Expected Output Returned payload includes external user data under `:user`. ## Failure Modes - missing/incorrect connection binding - upstream API errors or timeouts - transform output in a `:function` step when shaping logic grows or is reused across steps ## Try Next - [Scheduled Rollup](/docs/example-scheduled-rollup) - [Flow Configuration](/docs/guide-flow-configuration) --- Document: Scheduled Rollup URL: https://flows.breyta.ai/docs/example-scheduled-rollup.md HTML: https://flows.breyta.ai/docs/example-scheduled-rollup Last updated: 2026-05-10T21:06:10+02:00 # Scheduled Rollup Breyta scheduled workflow example for cron-based rollup jobs. ## Quick Answer Use top-level `:schedules` with cron config and `:concurrency :drain` for predictable handover between flow versions. Use a top-level function for output shaping, keeping the `:flow` body orchestration-only and labeling orchestration branches. ## Use Case Run periodic aggregation on a schedule. ## Full Definition ```clojure {:slug :scheduled-rollup :name "Scheduled Rollup" :concurrency {:type :singleton :on-new-version :drain} :functions [{:id :build-rollup-output :language :clojure :code "(fn [input] {:type :rollup :run-at (:run-at input) :input (:input input)})"}] :invocations {:default {:inputs []}} :schedules [{:id :every-15m :label "Every 15m" :invocation :default :enabled true :cron "*/15 * * * *"}] :flow '(let [run-at (flow/step :time :now {}) input (flow/input) rollup-input ^{:label "Rollup input source" :yes "Use override payload" :no "Use schedule/manual input"} (if (:override input) (:override input) input) output (flow/step :function :build-output {:ref :build-rollup-output :input {:run-at run-at :input rollup-input}})] output)} ``` ## Expected Behavior A new run executes on schedule and produces rollup payload metadata. ## Notes Use `:drain` when overlapping versions should finish before switching. Operational guidance: - top-level schedule means runs are time-based, not manually started by default - choose a short cron interval in non-prod during validation - keep output lightweight for periodic workloads - `:drain` lets in-flight runs finish before a new released version takes over Validation tip: 1. release flow 2. wait one or two schedule intervals 3. inspect latest runs and confirm `:run-at` changes per run ## Try Next - [Wait For Approval](/docs/example-wait-for-approval) - [Flow Features And Config Toggles](/docs/guide-flow-features-and-config-toggles) --- Document: Wait For Approval URL: https://flows.breyta.ai/docs/example-wait-for-approval.md HTML: https://flows.breyta.ai/docs/example-wait-for-approval Last updated: 2026-05-10T21:06:10+02:00 # Wait For Approval Breyta wait-for-approval example for human-in-the-loop orchestration using canonical wait config. ## Quick Answer Use `flow/step :wait` with `:key`, explicit `:timeout`, and a deterministic timeout outcome. Use a top-level function for approval-to-status mapping and label orchestration branch nodes for readable timelines. ## Use Case Pause execution until an approver chooses approve/reject (or timeout policy is applied). ## Full Definition ```clojure {:slug :wait-for-approval :name "Wait For Approval" :concurrency {:type :singleton :on-new-version :supersede} :functions [{:id :approval->status :language :clojure :code "(fn [input] (let [action (keyword (or (get-in input [:approval :action]) \"reject\"))] {:status (if (= :approve action) :approved :rejected)}))"}] :invocations {:default {:inputs [{:name :order-id :type :text :label "Order ID" :required true}]}} :interfaces {:manual [{:id :run :label "Run" :invocation :default :enabled true}]} :flow '(let [input (flow/input) approval (flow/step :wait :approval {:key (str "approval:order:" (:order-id input)) :timeout "1h" :on-timeout :continue :default-value {:action :reject :reason :timeout} :notify {:channels {:http {:connection :sendgrid :path "/v3/mail/send" :method :post :json {:personalizations [{:to [{:email "approver@example.com"}]}] :from {:email "approvals@example.com"} :subject "Approve Order {{order-id}}" :content [{:type "text/plain" :value "Order requires human review.\nApprove: {{approval-url}}\nReject: {{rejection-url}}"}]}}}}}) decision (flow/step :function :derive-status {:ref :approval->status :input {:approval approval}}) outcome ^{:label "Approval branch" :yes "Approved" :no "Rejected/timeout"} (if (= :approved (:status decision)) {:status :approved :approval approval} {:status :rejected :approval approval})] outcome)} ``` ## Expected Behavior Run pauses at `:approval` until action is submitted or timeout is reached. ## Operating Notes - set explicit `:timeout` for every wait - use business identifiers in wait key/message - treat timeout as a deliberate business branch, not an implicit error - include order id, concise reason, and enough context for a decision without external lookups Validation tip: 1. start run with an `:order-id` 2. confirm run enters waiting state 3. test approve/reject and timeout outcomes ## Try Next - [Retry And Compensation](/docs/example-retry-compensation) - [Waits, Signals, And Timeouts](/docs/guide-waits-signals-and-timeouts) --- Document: Retry And Compensation URL: https://flows.breyta.ai/docs/example-retry-compensation.md HTML: https://flows.breyta.ai/docs/example-retry-compensation Last updated: 2026-05-10T21:06:10+02:00 # Retry And Compensation Breyta retry and compensation example for resilient external side-effect workflows. ## Quick Answer Add `:retry` for transient failures and design explicit compensation for non-recoverable downstream failures. Keep result shaping in a function step so orchestration remains focused on retries, side-effect ordering, and labeled branches. ## Use Case Handle unreliable external calls and rollback side effects on failure. ## Full Definition ```clojure {:slug :retry-compensation :name "Retry And Compensation" :concurrency {:type :singleton :on-new-version :supersede} :requires [{:slot :billing :type :http-api :label "Billing API" :base-url "https://api.example.com" :auth {:type :bearer}}] :functions [{:id :build-summary :language :clojure :code "(fn [input] {:charge (:charge input) :receipt (:receipt input)})"}] :invocations {:default {:inputs [{:name :customer-id :type :text :label "Customer ID" :required true} {:name :amount-cents :type :number :label "Amount cents" :required true} {:name :skip-receipt :type :boolean :label "Skip receipt"}]}} :interfaces {:manual [{:id :run :label "Run" :invocation :default :enabled true}]} :flow '(let [input (flow/input) charge (flow/step :http :create-charge {:connection :billing :method :post :path "/charges" :body {:customer-id (:customer-id input) :amount-cents (:amount-cents input)} :retry {:max-attempts 3 :backoff-ms 1000}}) receipt ^{:label "Receipt dispatch" :yes "Send receipt" :no "Skip receipt"} (if (not (:skip-receipt input)) (flow/step :http :send-receipt {:connection :billing :method :post :path "/receipts" :body {:charge-id (:id charge)}}) {:status :skipped :reason :skip-receipt}) summary (flow/step :function :summarize-result {:ref :build-summary :input {:charge charge :receipt receipt}})] summary)} ``` ## Compensation Pattern If `send-receipt` fails permanently, add a compensating step to cancel/refund charge in your failure branch. - add compensation when side effects are partially committed and invariants require rollback - target retries at transient network/upstream instability, not deterministic validation/auth failures ## Try Next - [Advanced Multi-Feature Flow](/docs/example-advanced-multi-feature) - [Troubleshooting](/docs/guide-troubleshooting) --- Document: CLI Essentials URL: https://flows.breyta.ai/docs/cli-essentials.md HTML: https://flows.breyta.ai/docs/cli-essentials Last updated: 2026-05-21T12:24:31+02:00 # CLI Essentials Breyta CLI essentials for the default draft/live workflow. ## Quick Answer Use this default path: `breyta flows lint --file ./tmp/flows/.clj` -> `breyta flows push --file ./tmp/flows/.clj` -> `breyta flows configure --set .conn=conn-...` (if required) -> `breyta flows configure check ` -> `breyta flows run --wait` ## Core Checks | Check | Why | |---|---| | Confirm API/workspace/token context | Avoids writing to the wrong workspace. | | Search workspace patterns and approved templates before writing source | Reuses proven structure instead of spending context on blank-page design. | | Search resources before rebuilding data ingestion | Existing run outputs, reports, uploads, and table resources may already contain the data shape you need. | | Reuse connections before creating new ones | Reduces duplicate connection sprawl. | | Test connection health before wiring | Catches auth/base-url issues earlier, but it is not a substitute for a real flow run. | | Search existing workspace flows first | Reuse nearby workspace patterns before creating new slugs. | | Configure required bindings/secrets/inputs before first run | Prevents avoidable runtime failures. | | Validate before release | Prevents bad releases. | ## Commands | Command | Use | |---|---| | `breyta docs find "flows" --limit 5 --format json` | Find docs quickly in a parseable agent-friendly shape. | | `breyta flows search "" --limit 5` | Search actual workspace flows by metadata, step type, provider, connection, or tool name. | | `breyta flows grep "" --surface definition,tools --limit 5` | Search actual workspace flow source/config literals. Use repeatable `--or` for spelling variations and `--surface` to narrow noisy matches. | | `breyta flows templates search "" --limit 5` | Search approved reusable templates by metadata. Default cards are compact; use `--full` only for template source previews. | | `breyta flows templates grep "" --surface steps,tools --limit 5` | Search approved reusable template source/config literals. | | `breyta resources search "" --limit 5` | Search existing workspace resources, uploads, and prior run outputs before creating new ingestion. | | `breyta resources read --limit 5` | Read a compact preview of one selected resource. Use `--full` only when the complete payload is required. | | `breyta connections list [--type ...]` | Reuse an existing connection first. | | `breyta connections usages --only-connected` | Inspect where connections are already wired (`draft`/`live`/`installation`). | | `breyta connections items [--item-type ]` | Inspect cached item types and rows for connection-backed dropdowns. | | `breyta connections test ` | Test the specific connection you plan to bind or debug. Avoid `--all` unless the task is a workspace-wide connection audit. | | `breyta secrets usages --only-connected` | Inspect where secret refs are already wired (`draft`/`live`/`installation`). | | `breyta flows lint --file ./tmp/flows/.clj` | Fast local-first candidate source checks before mutating draft. Add `--server` for canonical non-mutating API lint. | | `breyta flows push --file ./tmp/flows/.clj` | Update working copy. | | `breyta help flows update` | Show mutable flow metadata flags, including grouped-flow ordering flags like `--group-order`. | | `breyta flows configure suggest ` | Suggest `--set` values from existing connections. | | `breyta flows configure check ` | Check missing bindings/inputs before running. | | `breyta flows validate ` | Optional read-only validation (CI/troubleshooting/explicit checks). | | `breyta flows diff ` | Inspect draft-vs-live source changes before release. | | `breyta docs show guide-flow-configuration --section "Required bindings"` | Open focused configuration guidance when flow requires bindings/secrets/inputs. Use `--full` only for the full page. | | `breyta flows run --wait` | Verify runtime behavior. The single manual interface is inferred; use `--interface-id ` only when selecting that declared manual interface explicitly. | | `breyta incidents list --status open` | Inspect active flow-health incidents in the current workspace. | | `breyta incidents list --status open --mine` | Limit incident inspection to flows you created in the current workspace. | | `breyta incidents show ` | Inspect one incident with failure rows and drill-down links. | | `breyta digests list --kind scheduled --cadence monthly` | Inspect scheduled digest artifacts on the current cadence. | | `breyta digests cadence` | Show the current scheduled digest cadence and settings deep link. | | `breyta digests cadence set monthly` | Change the scheduled digest cadence for the current workspace/user. | Configure bindings and activation inputs use the same `flows configure` surface: `--set .conn=` binds a declared slot, `--set .secret=` stores a required secret, and `--set activation.=` saves a setup input. Use `flows doctor` or `flows readiness` when checking target readiness. Search/read defaults are `rg`-like: bounded hits include a stable ref, matched field or surface, short snippet or preview, and a `nextCommand` for the smallest focused inspection. Escalate to `--full`, `--raw-definition`, or full resource reads only when the preview cannot answer the decision. Advanced rollout commands: - `breyta flows release --release-note-file ./release-note.md` (updates live installation for the selected workspace by default) - `breyta flows versions update --version --release-note-file ./release-note.md` - `breyta flows promote ` ## Flow Health Email Cadence - Flow-health incident/digest commands are currently available to workspace creators/admins. - `breyta digests cadence` changes only your own delivery settings in the current workspace. - In-app flow-health updates are on by default for your account. - Email flow-health updates are opt-in for your account. - Scheduled incident digests summarize surfaced incident changes from the selected `daily`, `weekly`, or `monthly` window. - If there are no surfaced incident changes in that window, no scheduled digest is sent. - Urgent incident updates are emailed as soon as possible when your email delivery mode includes urgent issues. - Urgent emails are not held until the next scheduled digest window. - Use `breyta digests cadence` or `breyta digests cadence set daily|weekly|monthly` to inspect or change the scheduled cadence from the CLI. ## Functional Difference: Draft Vs Live | Target | Updated by | Typical use | |---|---|---| | `draft` | `flows push` | Day-to-day authoring and validation. | | `live` | `flows release` or `flows promote` | Stable installed runtime target. | ## Run Targeting After Release `breyta flows run ` defaults to draft-target bindings/config. It still executes the active flow version unless `--version` is provided. Releasing does not automatically switch that default to a live installation target. | Need | Command | |---|---| | Run with draft-target bindings/config (default) | `breyta flows run --wait` | | Run installed live target | `breyta flows run --target live --wait` | | Run exact installation | `breyta flows run --installation-id --wait` | | Skip end-user installation promotion on release (advanced) | `breyta flows release --skip-promote-installations` | | Manually promote live installation | `breyta flows promote ` | Brand-new flows with no active version will return `no_active_version` on `flows run` until you deploy or release a version. If a brand-new flow has required slots, prepare the live target before the first release: - `breyta flows configure --target live --version latest --set .conn=` - `breyta flows configure check --target live --version latest` ## Related - [Flow Grouping](/docs/guide-flow-grouping) - [CLI Workflow](/docs/guide-cli-workflow) - [Author Flows](/docs/playbook-author-flows) - [Debug And Verify](/docs/playbook-debug-and-verify) - [Runtime Data Shapes](/docs/reference-runtime-data-shapes) - [CLI Commands](/docs/reference-cli-commands) - [Troubleshooting](/docs/guide-troubleshooting) --- Document: CLI Agent Onboarding URL: https://flows.breyta.ai/docs/cli-agent-onboarding.md HTML: https://flows.breyta.ai/docs/cli-agent-onboarding Last updated: 2026-05-21T12:24:31+02:00 # CLI Agent Onboarding Agent-first onboarding for Breyta (Codex, Claude Code, Cursor, Gemini CLI, and other agents that can run local CLI commands). ## Install the CLI If `breyta --version` fails, install the CLI: - macOS (Homebrew): - `brew tap breyta/tap` - `brew install breyta` - Prebuilt binaries: download a release artifact from https://github.com/breyta/breyta-cli/releases and put `breyta` on your `PATH`. - From source (Go): `go install github.com/breyta/breyta-cli/cmd/breyta@latest` ## Quick Answer Install the `breyta` CLI, then run: ```bash breyta init --provider breyta auth login breyta workspaces list breyta flows search "" --limit 5 breyta flows templates search "" --limit 5 ``` `breyta init` installs the Breyta skill bundle (optional) and creates a local `breyta-workspace/` folder with an `AGENTS.md` file that your agent can read reliably. The bundle includes `SKILL.md`, task playbooks, and focused references. Agents should read `SKILL.md` first, then load the playbook or reference named by its loading matrix before creating, editing, releasing, publishing, changing outputs/tables, touching provider APIs/models, or changing reliability behavior. That generated `AGENTS.md` is intended to push a connection-first, definition-first build order: inventory and test connections first, design `:requires` slots around business capabilities, then build top-level `:templates`, `:functions`, packaged `:steps`, reusable `:agents`, and only then the final `:flow` orchestration. ## Why `AGENTS.md` Matters Many agent tools only load project instructions from the active folder when you open it as a workspace/project (or when you start a new agent session in that directory). After `breyta init`, open `./breyta-workspace/` in your agent tool (or `cd` into it and restart the agent) so it picks up `AGENTS.md`. ## Copy/Paste Snippet (give this to your agent) ```text You are my Breyta onboarding assistant. Goal: install the Breyta CLI, set up reliable agent context (AGENTS.md), authenticate, then help me create and run my first flow. Please: 1) Check if `breyta` is installed: `breyta --version`. 2) If it isn’t installed, help me install it: - macOS (Homebrew): - `brew tap breyta/tap` - `brew install breyta` - manual download (all platforms): https://github.com/breyta/breyta-cli/releases - optional (if Go is installed): `go install github.com/breyta/breyta-cli/cmd/breyta@latest` 3) Ask which agent tool I’m using (Codex, Cursor, Claude Code, Gemini CLI) and run: - `breyta init --provider ` 4) Tell me to open the created `breyta-workspace/` folder in my agent tool (or `cd` into it and restart the agent) so it reads `AGENTS.md`. 5) Authenticate (and create an account if needed): `breyta auth login` 6) Verify: - `breyta workspaces list` - `breyta flows search "" --limit 5` - `breyta flows templates search "" --limit 5` - `breyta connections list` - `breyta connections test ` for the connection likely to be used 7) Help me build my first flow using the CLI loop: - first search reusable patterns and existing data: - `breyta flows search "" --limit 5` - `breyta flows grep "" --limit 5` when metadata is insufficient - `breyta flows templates search "" --limit 5` - `breyta flows templates grep "" --limit 5` when template metadata is insufficient - `breyta docs find "" --limit 5 --format json` - `breyta docs show --section ""` for one focused page section - `breyta resources search "" --limit 5` and `breyta resources read --limit 5` when the flow should reuse prior reports, uploads, or run output - then inventory and validate the reusable connections the flow will need: - `breyta connections list` - `breyta connections test ` for likely candidates - `breyta connections show ` for likely candidates - design stable `:requires` slot names around business capability (`:github-api`, `:crm`, `:llm`) rather than transient provider names - then shape the reusable flow surfaces before writing orchestration: - start with a small installable-minded contract: `:requires`, `:invocations`, `:interfaces`, output shape, side effects, and `:concurrency` - `:templates` for prompts, request bodies, and large static content - `:functions` for normalization, shaping, and result projection - `:steps` for packaged heavy built-in step configs - `:agents` for reusable reviewer/fixer/coordinator agent definitions - then wire them together in `:flow` - keep iterations small: - push a minimal manual-interface skeleton before adding integrations - add one connection, provider call, table, job, agent, or child flow at a time - default to `:persist` for unknown/unbounded payloads and pass resource refs instead of large inline bodies - when transforming a persisted blob, pass the persisted step result into a `:function` step and hydrate it with `:load` - keep functions map-oriented; use Clojure map access plus `json/*` and `breyta.sandbox/*` helpers instead of custom parser/guard layers - use bounded paging with max pages/items and durable writes before cursor advancement for larger datasets - design the final output artifact before implementing the last expression: - default to a readable Markdown viewer for human-facing flows - use `breyta-resource` fenced blocks for embedded tables, charts, downloads, media, nested Markdown, text, and JSON resources - do not expose raw debug maps, `res://` refs, or implementation fields unless structured data is the intended product - `breyta flows pull --out ./flows/.clj` - edit `./flows/.clj` - `breyta flows lint --file ./flows/.clj` - `breyta flows push --file ./flows/.clj` - `breyta flows configure check ` - `breyta flows run --input '{"n":41}' --wait` - flows may declare one manual interface; if you need explicit selection, use `breyta flows run --interface-id --input '{"n":41}' --wait` - model alternate manual entry modes as invocation inputs such as `mode`, not as multiple manual interfaces - optional: `breyta flows validate ` - inspect draft changes before release: `breyta flows diff ` - iterate in draft until behavior is correct - release once after explicit approval, with a markdown note: `breyta flows release --release-note-file ./release-note.md` - edit the version note later if needed: `breyta flows versions update --version --release-note-file ./release-note.md` - verify live target: `breyta flows show --target live` - smoke-run live target: `breyta flows run --target live --wait` 8) Use product docs when needed: - search by primitive: `breyta docs find "files materialize" --limit 5 --format json` - search by exact phrase: `breyta docs find "\"draft setup\"" --limit 5 --format json` - search by command path: `breyta docs find "source:cli flows configure check" --limit 5 --format json` - search by API/runtime source: `breyta docs find "source:flows-api agent definitions" --limit 5 --format json` - search by error text when stuck: `breyta docs find "\"Bad credentials\"" --limit 5 --format json` - `breyta docs show ` for the best narrow hit; use `--section ` first and `--full` only when the complete page is needed 9) Use CLI help for flag truth: - `breyta help ` 10) Use the installed Breyta skill bundle when working on flows: - read `SKILL.md` first - load the relevant bundled playbook or `references/` file before creating or editing behavior - use `playbooks/author-flows.md` for create/edit/template/resource-ref work - use `playbooks/debug-and-verify.md` for failed runs, output proof, and UI mismatch - use `playbooks/release-and-install.md` for draft/live/release/install work - use `playbooks/public-and-marketplace.md` for public/end-user/Discover/marketplace work - use `playbooks/advanced-reliability.md` for fanout, paging, waits, retries, and idempotency - use `references/outputs-and-tables.md` for outputs, Markdown reports, resource embeds, raw viewers, JSON resource embeds, persisted resources, and table viewers - use `references/runtime-data-shapes.md` for resource refs, `:persist`, `:load`, and function input shapes - use `references/provider-api-freshness.md` for external APIs and model ids Prefer small, incremental changes and verify each step before moving on. ``` ## Make Skills Reliable (Optional) Some tools auto-load skills; others do not. If you want your agent to always use the Breyta skill bundle when Breyta is involved: 1) install it (or refresh it) with `breyta skills install --provider all` or a single provider such as `--provider codex` 2) explicitly reference the installed skill file path from your persistent project instructions (for example `AGENTS.md`) Typical install paths: - Codex: `~/.codex/skills/breyta/SKILL.md` - Claude Code: `~/.claude/skills/breyta/SKILL.md` - Cursor: `~/.cursor/rules/breyta/RULE.md` - Gemini CLI: `~/.gemini/skills/breyta/SKILL.md` Suggested compact line for `AGENTS.md`: > For Breyta work, load the installed Breyta skill first, then only the playbook/reference for the touched surface. Treat missing/stale skill warnings as drift. Use bounded search (`docs find`, `flows search/grep`, `flows templates search`, `resources search` with `--limit 5`). Build small draft slices, lint before push, pass large data as resource refs, and verify the same interface/input/target the user will exercise. Draft is staging; live is released/runtime. The installed bundle includes `SKILL.md`, `playbooks/`, and `references/`. The compact skill contains the loading matrix. Load one relevant playbook before editing or creating a flow, changing public install surfaces, adding table/resource output, touching external provider APIs/models, or changing fanout/concurrency behavior. --- Document: Advanced Multi-Feature Flow URL: https://flows.breyta.ai/docs/example-advanced-multi-feature.md HTML: https://flows.breyta.ai/docs/example-advanced-multi-feature Last updated: 2026-05-20T03:00:25+02:00 # Advanced Multi-Feature Flow Complex production example combining webhook ingestion, templates, email notifications, human approvals, retries, and persistence. ## Quick Answer Use this pattern when you need one flow to process webhook events, send templated email notifications, gate with approval, and persist artifacts/audit state. ## Use Case Order risk workflow with: - webhook/manual interfaces + top-level schedule - template-driven HTTP/notification payloads - human approval wait - email notifications via SendGrid HTTP channel - persisted result artifacts (`res://`) - KV audit write for cross-run lookup ## Full Definition ```clojure {:slug :advanced-order-orchestration :name "Advanced Order Orchestration" :concurrency {:type :keyed :key-field :order-id :on-new-version :drain} :requires [{:slot :orders-api :type :http-api :label "Orders API" :base-url "https://api.example.com" :auth {:type :bearer}} {:slot :sendgrid :type :http-api :label "SendGrid API" :base-url "https://api.sendgrid.com" :auth {:type :bearer}} {:slot :webhook-signing-secret :type :secret :label "Webhook Signing Secret"}] :templates [{:id :fetch-risk :type :http-request :request {:method :get :path "/orders/{{order-id}}/risk"}} {:id :approval-notice :type :notification :channels {:http {:connection :sendgrid :path "/v3/mail/send" :method :post :json {:personalizations [{:to [{:email "approver@example.com"}]}] :from {:email "approvals@example.com"} :subject "Approve order {{order-id}}" :content [{:type "text/plain" :value "Approve: {{approval-url}}\nReject: {{rejection-url}}"}]}}}}] :functions [{:id :risk-summary :language :clojure :code "(fn [risk] {:risk-level (:risk-level risk) :risk-score (:score risk) :needs-approval (= :high (:risk-level risk))})"}] :invocations {:default {:inputs [{:name :order-id :type :text :label "Order ID" :required true}]}} :interfaces {:manual [{:id :run :label "Run" :invocation :default :enabled true}] :webhook [{:id :order-updated :invocation :default :event-name "orders.updated" :auth {:type :hmac-sha256 :header "Stripe-Signature" :secret-ref :webhook-signing-secret}}]} :schedules [{:id :hourly :label "Hourly" :invocation :default :enabled true :cron "0 * * * *"}] :flow '(let [input (flow/input) order-id (:order-id input) order (flow/step :http :fetch-order {:connection :orders-api :method :get :path (str "/orders/" order-id) :retry {:max-attempts 3 :backoff-ms 500}}) risk (flow/step :http :fetch-risk {:connection :orders-api :template :fetch-risk :data {:order-id order-id} :retry {:max-attempts 3 :backoff-ms 1000}}) summary (flow/step :function :risk-summary {:ref :risk-summary :input risk}) risk-artifact (flow/step :function :persist-risk {:input {:order order :risk risk :summary summary} :persist {:type :blob}}) approval ^{:label "Manager approval required?" :yes "Wait for manager action" :no "Auto-approve"} (if (:needs-approval summary) (flow/step :wait :manager-approval {:key (str "approval:order:" order-id) :timeout "2h" :on-timeout :continue :default-value {:action :reject :reason :timeout} :notify {:template :approval-notice :data {:order-id order-id :risk-score (:risk-score summary)}}}) {:action :approve :reason :auto}) action (keyword (or (:action approval) "reject")) fulfillment ^{:label "Fulfillment decision" :yes "Create fulfillment" :no "Skip fulfillment"} (if (= :approve action) (flow/step :http :fulfill-order {:connection :orders-api :method :post :path (str "/orders/" order-id "/fulfill") :retry {:max-attempts 4 :backoff-ms 1500}}) {:status :not-fulfilled}) _audit (flow/step :kv :write-audit {:operation :set :key (str "orders:audit:" order-id) :value {:order-id order-id :action action :risk-level (:risk-level summary) :artifact-ref (:uri risk-artifact)} :ttl 604800}) notify (flow/step :notify :send-outcome-email {:channels {:http {:connection :sendgrid :path "/v3/mail/send" :method :post :json {:personalizations [{:to [{:email "ops@example.com"}]}] :from {:email "alerts@example.com"} :subject "Order {{order-id}} result" :content [{:type "text/plain" :value "action={{action}}, artifact={{artifact-ref}}"}]}}} :data {:order-id order-id :action (name action) :artifact-ref (:uri risk-artifact)}})] {:order-id order-id :action action :approval approval :risk-summary summary :risk-artifact-ref (:uri risk-artifact) :fulfillment fulfillment :notification notify})} ``` ## Explanation Highlights 1. Webhook ingress is modeled as `:interfaces :webhook` with explicit auth. 2. Risk payload is persisted as `res://` to avoid large inline state. 3. Wait step uses canonical `:key` + `:timeout` + `:on-timeout` semantics. 4. Branch metadata labels (`^{:label ...}` with `:yes`/`:no`) make decisions readable in run timelines. 5. Approval notification template drives email-like HTTP channel delivery with action links. 6. Notify step sends final outcome email through SendGrid HTTP API. 7. KV write stores compact audit state for later lookup. ## Operational Notes - keep webhook auth on; avoid unauthenticated public endpoints - keep wait timeouts bounded and branch timeout behavior explicitly - treat `:http` channel as the canonical email delivery path - watch result sizes: persist early when output may exceed the 512 KB inline result threshold; 1 MB is the hard step/DB/code/notify payload cap, while persisted blob writes cap at 50 MB retained or 4 GB ephemeral ## Related - [Webhooks And Secret Refs](/docs/guide-webhooks-and-secret-refs) - [Notifications And Email](/docs/guide-notifications-and-email) - [Persisted Results And Resource Refs](/docs/guide-persisted-results-and-resources) - [Limits And Recovery](/docs/reference-limits-and-recovery) --- Document: Flow Grouping URL: https://flows.breyta.ai/docs/guide-flow-grouping.md HTML: https://flows.breyta.ai/docs/guide-flow-grouping Last updated: 2026-05-15T14:59:18+02:00 # Flow Grouping Use `breyta flows update` to manage mutable flow metadata for grouped flows, including `groupKey`, `groupName`, `groupDescription`, and explicit `groupOrder`. ## Quick Answer Use this loop when related flows should render in a deliberate sequence: 1. Inspect current grouping metadata with `breyta flows show ` or `breyta flows list --limit 50` when you need inventory 2. Set or change grouping metadata with `breyta flows update ...` 3. Verify sibling order with `breyta flows show ` Use `breyta help flows update` for exact flag truth. ## What Lives In Group Metadata Flow grouping is mutable workspace metadata. It does not round-trip through `breyta flows pull` / `breyta flows push` source files. That means: - use the pulled `.clj` file for authored flow logic - use `breyta flows update` for grouping changes - verify grouped siblings through `flows show ` / `flows list --limit 50` ## Set Or Update A Group Create or update grouping metadata on a flow: ```bash breyta flows update invoice-start \ --group-key invoice-pipeline \ --group-name "Invoice Pipeline" \ --group-description "Flows that run in sequence for invoice processing" ``` Rules: - `groupName` is required whenever `groupKey` is set - `groupKey` is the stable identifier for the group - `groupDescription` is optional, but use one shared description across siblings when possible ## Set Explicit Group Order Use `groupOrder` when grouped flows should render in execution order: ```bash breyta flows update invoice-start --group-order 10 breyta flows update invoice-validate --group-order 20 breyta flows update invoice-approve --group-order 30 ``` Ordering rules: - lower `groupOrder` values sort first - use spaced values like `10`, `20`, `30` so future insertions are easy - `flows show ` returns sibling `groupFlows` ordered by explicit `groupOrder` when present ## Verify Grouping Use these read paths after any grouping change: ```bash breyta flows list --limit 50 breyta flows show invoice-start breyta help flows update ``` Look for: - `groupKey` - `groupName` - `groupDescription` - `groupOrder` - `groupFlows` ## Clear Order Or Clear The Whole Group Clear only the explicit order: ```bash breyta flows update invoice-approve --group-order "" ``` Clear the entire group membership from a flow: ```bash breyta flows update invoice-approve --group-key "" ``` ## Related - [CLI Workflow](/docs/guide-cli-workflow) - [CLI Commands](/docs/reference-cli-commands) - [CLI Essentials](/docs/cli-essentials) --- Document: CLI Skills URL: https://flows.breyta.ai/docs/cli-skills.md HTML: https://flows.breyta.ai/docs/cli-skills Last updated: 2026-05-15T14:59:18+02:00 # CLI Skills Breyta CLI skills setup guide for Codex, Cursor, Claude Code, and Gemini CLI agent runtimes. ## Quick Answer Recommended: run `breyta init --provider ` to install the Breyta skill bundle and create a local `breyta-workspace/` with an `AGENTS.md` file for reliable agent context. The generated `AGENTS.md` uses draft-first release hygiene: iterate on draft, then release to live once after approval. It also front-loads the current core authoring pattern: inventory and test connections first, shape `:requires` around stable capability slots, then build reusable definitions (`:templates`, `:functions`, packaged `:steps`, `:agents`) before filling in `:flow`. For human-facing flows, the skill bundle also tells agents to design the final output artifact up front: usually a Markdown report, with `breyta-resource` embeds when the report needs real Breyta tables, charts, downloads, media, text, or JSON resources. If you only want the skill bundle, use `breyta skills install --provider `. ## Do This Now Recommended (skill + workspace instructions): ```bash breyta init --provider codex breyta init --provider cursor breyta init --provider claude breyta init --provider gemini ``` Skill-only: ```bash breyta skills install --provider codex breyta skills install --provider cursor breyta skills install --provider claude breyta skills install --provider gemini ``` Install for the provider used by your active runtime (`codex`, `cursor`, `claude`, or `gemini`). ## What This Does - installs Breyta skill files to provider-specific locations - installs `SKILL.md` plus bundled `playbooks/` and `references/` files for task-specific guidance - enables agent-assisted flow authoring and operations - does not change deployed flow runtime behavior - `breyta init` also creates a folder with `AGENTS.md` to keep instructions in the agent context - the generated guidance biases agents toward connection-first, definition-first authoring instead of putting heavy config directly in `:flow` - the compact `SKILL.md` tells agents which playbook or reference to load for authoring, debugging, release/install, public flows, output rendering/resources/tables, provider API freshness, and reliability work ## When To Use Use when setting up or updating an agent environment. ## Advanced Options - use provider-specific install targets - verify install artifacts with local file checks ## Go Deeper - [CLI Agent Onboarding](/docs/cli-agent-onboarding) - [Breyta CLI Skill](/docs/breyta-cli-skill) - [Playbook: Author Flows](/docs/playbook-author-flows) - [Playbook: Debug And Verify](/docs/playbook-debug-and-verify) - [Playbook: Release And Install](/docs/playbook-release-and-install) - [Playbook: Public And Marketplace](/docs/playbook-public-and-marketplace) - [Playbook: Advanced Reliability](/docs/playbook-advanced-reliability) - [Breyta Skill: Public Flows](/docs/breyta-cli-skill-public-flows) - [Breyta Skill: Outputs And Tables](/docs/breyta-cli-skill-outputs-and-tables) - [Breyta Skill: Provider And API Freshness](/docs/breyta-cli-skill-provider-api-freshness) - [Breyta Skill: Reliability And Concurrency](/docs/breyta-cli-skill-reliability-and-concurrency) - [Runtime Data Shapes](/docs/reference-runtime-data-shapes) - [CLI Commands](/docs/reference-cli-commands) - [CLI Essentials](/docs/cli-essentials) --- Document: Playbook: Author Flows URL: https://flows.breyta.ai/docs/playbook-author-flows.md HTML: https://flows.breyta.ai/docs/playbook-author-flows Last updated: 2026-05-21T12:24:31+02:00 # Playbook: Author Flows Load when creating or editing a Breyta flow, adding steps, changing inputs, copying template patterns, or preparing a draft for release. ## Mental Model Flow definitions are a Breyta DSL with Clojure/EDN syntax. They are not normal Clojure programs. The DSL describes an orchestration graph plus contracts: `:requires`, `:invocations`, `:interfaces`, `:concurrency`, reusable `:templates`, `:functions`, packaged `:steps`, `:agents`, and finally `:flow`. Author with platform limits in mind: deterministic orchestration, explicit step boundaries, serializable maps/vectors, bounded loops, persisted large payloads, and live/install targets that may differ from draft. For first proof, inline small transform functions and step bodies when that is the shortest path to a green draft run. Extract repeated or bulky pieces into top-level `:functions`, `:templates`, or packaged `:steps` after the first proof, before release or marketplace review. ## Default Loop 1. Start a small session capsule: - workspace, flow slug, task mode - intended interface and target - refs consulted - chosen pattern - next proof command - risks 2. Reuse before writing: - `breyta flows search "" --limit 5` - `breyta flows grep "" --surface definition,tools --limit 5` when metadata is not enough - `breyta flows templates search "" --limit 5` - `breyta flows templates grep "" --surface steps,tools --limit 5` when template metadata is not enough - `breyta resources search "" --limit 5` when prior uploads, reports, or run outputs may be reused - treat hits like `rg`: carry forward `hitRef`, matched field/surface, snippet/preview, and `nextCommand`; open one focused target, not every hit - for existing-flow edits, inspect the current flow before comparing examples - in clearly empty hosted workspaces, skip proving absence and start from the requested outcome 3. Define the contract before source grows: - `:requires` - `:invocations` - `:interfaces` - `:concurrency` - output shape - side effects and failure behavior 4. Build a minimal draft slice with one manual interface and one meaningful boundary. Avoid solving every integration in the first flow version. 5. Run fast feedback before mutating draft: - `breyta flows lint --file ./flows/.clj --local-only` for offline static checks - `breyta flows lint --file ./flows/.clj --server` when API context is available and canonical pre-push checks matter - `breyta flows push --file ./flows/.clj` - `breyta flows configure check ` when the flow has required slots - `breyta flows validate ` after push for stored draft/live validation 6. Prove the same entrypoint a user will exercise: - `breyta flows run --interface-id --input '' --wait` - omit `--interface-id` when the single manual interface can be inferred - keep at most one manual interface; use invocation inputs for mode choices - use `--target live` or `--installation-id` for release/install proof - inspect one step with `breyta runs inspect --step ` before expanding the full run payload ## Stop Discovery When - you have current state or a clear blank-workspace signal - one nearby workspace/template pattern is enough - docs for touched primitives are known - the next command is an edit, lint, push, configure check, run, or resource read Do not repeat identical search/help/docs commands unless state or the question changed. If development needs excessive trial/error, docs/help are misleading, a command shape is blocked or unclear, or examples do not cover the authoring path, submit `breyta feedback send` with the commands tried, URLs/workflow ids, expected path, and the friction. This helps Breyta evaluate whether the agent loop is working. ## Complex Flow Phase Gates Complex flows should still move in small slices. Start with this authoring playbook, then phase-load only the next surface: - provider/model/API call: `references/provider-api-freshness.md` - paging, fanout, child flows, waits, checkpoints, or retries: `playbooks/advanced-reliability.md` - persisted reports, tables, files, media, or large outputs: `references/outputs-and-tables.md` and `references/runtime-data-shapes.md` - release, install, public, Discover, or marketplace work: the release/public playbooks Before source grows, define a proof ladder and reuse it across `steps run` and `flows run`: - skeleton interface run with one safe input - one-record or one-page local JSON fixture - representative small batch - empty/failure fixture - live or install-shaped input For irreversible side effects such as email, CRM writes, billing, or external mutations, build preview/dry-run behavior, an approval wait, or an explicit author approval checkpoint before real writes. Add idempotency keys or dedupe guards when retries or reruns can repeat a write. ## Progressive Disclosure Use search result cards first. Open the full template/source only for: - cross-step architecture reuse - install behavior - parent/child or fanout structure - unclear dependencies - public/marketplace patterns Prefer primitive snippets and only copy referenced dependencies: `:requires`, `:templates`, `:functions`, packaged `:steps`, and `:agents`. Do not open a full template definition unless snippet context is insufficient or architecture, install behavior, fanout, child-flow structure, or unclear dependencies require it. For step config questions, use field lookup before opening a full reference: - `breyta docs fields http` for a compact config-object overview - `breyta docs fields http response-as persist retry --format json` for selected fields - `breyta docs fields files source paths --section read` when a step page has operation-specific tables - `breyta docs show reference-step-http --section "Canonical Shape"` only when the field rows are not enough ## Single-Step Development Use single-step commands to reduce full workflow reruns while developing a touched primitive: - `breyta steps run --type --id --params ''` - `breyta steps run --flow --source draft --type --id --params ''` - `breyta steps run --flow --source draft --type --id --params-file ./params.json` - `breyta steps record --flow --source draft --type --id --params ''` Use `--flow` when the step depends on flow-local `:templates`, `:functions`, `:requires`, packaged `:steps`, or installation bindings. Use `--source draft` while iterating on a just-pushed draft; use `--source active` or `--version ` when verifying released behavior. Add `--installation-id ` when slot-backed connections should resolve as they will for an installed run. Use inline `--params ''` for small inputs. Switch to `--params-file` when the payload is long, reused, or easier to edit outside the command line. Build the params from the smallest input map that exercises the touched primitive. Good candidates are pure functions, HTTP/API request shape, LLM prompt shape, file/resource read/write payloads, and notification payloads. Use care with real side effects, waits, fanout, child flows, callbacks, and params mostly produced by previous pipeline state. `steps run` accepts JSON but normalizes safe step-config keys to the authored Clojure shape. For example, `responseAs` becomes `:response-as` and `persist.type` can become `:persist {:type :blob}`. HTTP payload surfaces such as `headers`, `query`, `body`, `json`, and `form` keep their exact external key shape. `steps run` returns compact output by default: `data.resultPreview.value` is an EDN-style preview of the value as it would appear in a downstream `let` binding. Use `--result-path rows.0` or `--result-path '[:rows 0]'` to focus one branch, `--preview-depth`, `--preview-items`, and `--preview-runes` for bounded expansion, `--result-file ./tmp/result.json` to keep the full value locally, and `--full` only when the complete `data.result` belongs in context. Isolated step runs and stored step test verifies are real executions for accounting: they authorize against workspace billing state, create an audit `workflowId`, and count completed or failed step attempts toward step usage. Single-step proof is not final proof. After the primitive works, push and run the authored flow interface with the intended target, input, bindings, and side effect path. ## Interfaces First New callable flows should use `:interfaces` and `:invocations`. `--trigger-id` is legacy compatibility. For manual smoke runs, prefer `--interface-id ` so the CLI exercise matches the authored user entrypoint. ## Persist And Resource Refs Default to `:persist` for unknown or unbounded payloads: exports, paged API responses, files, transcripts, generated media, long Markdown, or row sets. Choose blob tier deliberately: retained/default for durable or user-visible artifacts; `:tier :ephemeral` on streaming `:http` steps for temporary downloads, exports, generated media, and response blobs. Function/table/KV persists remain retained/default today. Persisted step results are compact refs. Downstream steps should pass refs through maps and explicitly hydrate only where needed, for example a function step with `:input {:resp http-step-result}` and `:load [:resp]`. ## Function Shape Most runtime values are already Clojure maps. Keep `:function` steps small: - use `get`, `get-in`, `select-keys`, `update`, `mapv`, and destructuring - use `json/parse` and `json/write-str` for JSON - use `breyta.sandbox/*` helpers for common safe utilities - avoid large custom parser/guard layers unless external input is truly ambiguous ## Installable Mindset Assume a new or edited flow may become installable later: - keep workspace-specific ids, user ids, secrets, emails, private URLs, and model choices out of source defaults - put installer/user-specific values in `:requires`, setup inputs, run inputs, connections, or resources - split child flows or packaged steps when isolated testing and reuse are clearer than one large orchestration - keep `:flow` focused on orchestration; reusable structure belongs in `:requires`, `:templates`, `:functions`, packaged `:steps`, and reusable `:agents` - grouping is mutable display metadata, not source. Verify `groupFlows` with `breyta flows list --pretty` or `breyta flows show --pretty`; runtime linkage still comes from `flow/call-flow`, `:fanout`, or another primitive ## References - [Runtime Data Shapes](/docs/reference-runtime-data-shapes) - [Flow Definition](/docs/reference-flow-definition) - [Step Reference](/docs/reference-step-reference) - [Output Artifacts](../reference/REFERENCE_OUTPUT_ARTIFACTS.md) - [Templates](/docs/reference-templates) --- Document: Playbook: Debug And Verify URL: https://flows.breyta.ai/docs/playbook-debug-and-verify.md HTML: https://flows.breyta.ai/docs/playbook-debug-and-verify Last updated: 2026-05-19T10:42:46+02:00 # Playbook: Debug And Verify Load when a run fails, output is wrong, CLI and UI disagree, or proof needs to be collected before closing work. ## Default Loop 1. Identify target and lifecycle: draft, live, installation, version, interface, input, and workspace. 2. Inspect the failing run or command result. Keep workflow id, output URL, resource URIs, error code, and recovery action URLs in the session capsule. Prefer exact recovery links from `error.actions[].url` first, then `meta.webUrl`. - `breyta runs events --limit 100` returns a bounded timeline of run and step events for live debugging, including in-flight step updates when the execution store has them. Add `--step ` for one step, or `--installation-id ` when inspecting a run through an installed profile. - `breyta runs inspect --step ` returns compact step input/output/error/cost without opening the whole run payload. - `breyta runs step ` is the short alias for the same step I/O inspection and includes that step's event timeline when available. Add `--full` only when captured step output/error payloads are required. 3. Check for target mismatch: - `flows push` updates draft only - `flows release` or `flows promote` updates live - UI install paths usually follow live/install targets 4. Narrow to the touched primitive: - read the step config - run a focused docs search for that primitive - use `breyta steps run` or `breyta steps record` when the step can be tested outside the full flow - add `--flow --source draft` for draft-only templates/functions or `--installation-id ` for installed connection slots - start with inline `--params ''` for small inputs; move to `--params-file` when the input gets long or reused - inspect compact `data.resultPreview.value` first; expand with `--result-path`, `--preview-depth`, `--preview-items`, `--preview-runes`, or `--result-file` before using `--full` 5. Rerun the smallest flow entrypoint that can prove the fix: - manual: `breyta flows run --interface-id --input '' --wait` - HTTP: `breyta flows interfaces call --input '' --wait` - live/install: add `--target live` or `--installation-id ` - when a run is waiting for approval, use `breyta runs continue --approve-latest-wait` to approve the latest active approval wait, then inspect the run again 6. Read back user-facing output and side effects: - `breyta resources read --limit 20` - table query/read commands for table resources - external system records, emails, files, or callbacks when those are the proof - rendered notifications/emails: recipient, sender, subject, body, required wording, and absence of debug fields Do not rely on terminal status alone after a run returns output or artifacts. Inspect the actual user-facing artifact from the intended user's perspective. ## Escalate Only If - two edit/run cycles do not isolate the cause - the same command result conflicts with docs/help - a UI surface fails while CLI proof succeeds - a public/install path cannot be verified from available tools When platform behavior appears wrong, submit `breyta feedback send` with full URLs, workflow ids, output/resource URLs, target, interface id, and steps tried. Also submit feedback when the fix required excessive trial/error, docs/help were misleading, or CLI/API behavior made the intended agent loop unclear. ## Proof Contract Final proof should name: - command run - workflow id - target/version/installation - interface id or HTTP interface id - output/resource URLs - side effect evidence - unverified surfaces, especially `web UI not verified` ## References - [Runtime Data Shapes](/docs/reference-runtime-data-shapes) - [Flow Jobs](/docs/reference-flow-jobs) - [Output Artifacts](../reference/REFERENCE_OUTPUT_ARTIFACTS.md) - [Limits And Recovery](/docs/reference-limits-and-recovery) --- Document: Playbook: Release And Install URL: https://flows.breyta.ai/docs/playbook-release-and-install.md HTML: https://flows.breyta.ai/docs/playbook-release-and-install Last updated: 2026-05-21T12:24:31+02:00 # Playbook: Release And Install Load when moving draft behavior to live, configuring install targets, testing released behavior, or changing activation/setup/run forms. ## Draft And Live `draft` is the staging/current workspace authoring state. `live` is the released runtime state. A flow is unreleased until a version is released or promoted and the live/install-shaped path is verified. Say `draft verified` when only draft was exercised. Reserve `released`, `end-user ready`, and `public ready` for live or installation proof. For installable/public flows, activation/setup proof is not Discover install proof. Verify the installed or public catalog path before calling the flow ready. ## Default Loop 1. Start with one compact readiness report: - `breyta flows readiness ` - `breyta flows release-check --public --marketplace` for public paid release gates 2. Inspect draft/live difference only when the readiness report says it matters: - `breyta flows diff ` - `breyta flows show --target live` when live already exists 3. Check bindings and required inputs: - `breyta flows configure check ` - `breyta flows configure check --target live --version latest` 4. Prepare live target when required: - `breyta flows configure --target live --version latest --set .conn=` - use `--from-draft` only when intentionally copying draft setup to live 5. Release with an explicit note: - `breyta flows release --release-note-file ./release-note.md` 6. Prove live/install behavior: - `breyta flows run --target live --interface-id --input '' --wait` - installation-specific proof should use `--installation-id ` where relevant 7. Report the live version, run, output, and any install/setup URLs returned by the CLI. ## Interface Consistency Manual runs can use `--interface-id ` to select the declared manual interface explicitly. Flow source may declare at most one manual interface; use invocation inputs for manual mode choices. HTTP, webhook, and MCP entrypoints are interfaces, not legacy triggers. ## Stop When - live target configuration is valid - representative live/install run succeeded - user-facing output or side effects were inspected - unresolved install/UI surfaces are named as risks ## References - [Flow Configuration](/docs/guide-flow-configuration) - [Installations](/docs/guide-installations) - [Flow Definition](/docs/reference-flow-definition) - [Output Artifacts](../reference/REFERENCE_OUTPUT_ARTIFACTS.md) --- Document: Playbook: Public And Marketplace URL: https://flows.breyta.ai/docs/playbook-public-and-marketplace.md HTML: https://flows.breyta.ai/docs/playbook-public-and-marketplace Last updated: 2026-05-21T15:31:38+02:00 # Playbook: Public And Marketplace Load when changing Discover visibility, marketplace visibility, public copy, pricing, install behavior, or public/end-user output. ## Approval Boundary Public visibility, marketplace visibility, paid-flow settings, release, and promotion require explicit author approval. Keep authoring, live version, activation/setup, Discover install, marketplace, public app page, installed run, and output page as separate proof surfaces. The public marketing app page is always derived from the flow slug: `https://breyta.ai/apps/`. Include that URL in public handoff proof alongside the workspace install and Discover surfaces. Do not treat "make this a public app" as approval to expose the flow to all Breyta users. Do not set `:discover {:public true}`, `:marketplace {:visible true}`, or equivalent CLI flags until the author explicitly approves and the flow has installable-ready proof. ## Default Loop 1. Confirm the requested public surface: - Discover listing - marketplace visibility - paid/pricing - public app page copy - install/setup/run behavior - for paid source-authored apps, whether the catalog belongs under `:marketplace {:app {... :monetization {:plans [...]}}}` or an existing legacy `:marketplace {:monetization ...}` listing must be preserved 2. Inspect current state: - `breyta flows show ` - `breyta flows discover search "" --include-own --limit 5` when debugging own public indexing 3. Keep installable values out of source: - use `:requires`, setup inputs, run inputs, connection slots, templates, and resources - avoid hardcoded workspace ids, user ids, emails, private URLs, and secrets 4. Verify release/install shape, not draft alone: - `breyta flows show --target live` - `breyta flows run --target live --interface-id --wait` - installation commands or UI proof when install behavior changed - for HTTP/MCP public flows, inspect installed interfaces with `breyta flows installations interfaces ` and run one installation-scoped interface smoke test 5. Use supported CLI/API operations directly once approved; ask only for true author decisions such as visibility, exact price, currency, paywall timing, free runs, and public claims. 6. Inspect public output from the intended audience perspective. Markdown, tables, media, and resource viewers should be readable without raw debug payloads. 7. Consider step-level metering or cost estimates for paid external API, LLM, search, and other provider calls. State when cost metadata is unavailable. 8. Report all relevant URLs: source flow, live version, activation/setup, Discover/public listing, `https://breyta.ai/apps/`, marketplace/app page, run, output, and resources. ## Common Pitfalls - `/activate` is setup proof, not Discover install proof. - Template search is not public catalog proof. - Draft CLI proof is not public-ready proof. - The workspace install URL is not the marketing app URL. Use `https://breyta.ai/apps/` for public app-page proof. - Public copy should explain audience, setup requirements, run input, integrations, outputs, limits, and failure expectations. - New paid apps should use app-owned plan catalogs. Keep plan ids stable, use at most one default/recommended plan, and do not describe seat pricing unless explicit seat entitlements exist. ## References - [Paid Public Flows](/docs/guide-paid-public-flows) - [Installations](/docs/guide-installations) - [Flow Metadata](/docs/reference-flow-metadata) - [Output Artifacts](../reference/REFERENCE_OUTPUT_ARTIFACTS.md) --- Document: Playbook: Advanced Reliability URL: https://flows.breyta.ai/docs/playbook-advanced-reliability.md HTML: https://flows.breyta.ai/docs/playbook-advanced-reliability Last updated: 2026-05-15T14:59:18+02:00 # Playbook: Advanced Reliability Load when work involves fanout, paging through large datasets, concurrency, checkpoints, retries, waits, long-running jobs, or large artifacts. ## Default Loop 1. Decide whether the shape is a loop, fanout, child flow, table query, or external async job. 2. Bound the work: - max pages - max items - timeout - retry policy - concurrency limit - checkpoint key 3. Persist data that can grow: - each page - final row set - generated files/media - external exports 4. Return compact proof from each unit: - counts - cursor/checkpoint - resource refs - failed ids - retryable/non-retryable status 5. Give each side-effectful unit an idempotency key or dedupe key before adding retries, fanout, or reruns. 6. Test one page or one shard first, then a representative bounded batch, then the full intended run. ## Paging Shapes Use explicit loops for larger datasets. Suitable shapes include: - a packaged step or `:function`-backed step that pages a provider API up to `max-pages` or `max-items` - table resource queries with explicit `:page` - child flows for page-level work when each page is heavy or side-effectful - checkpoint state that advances only after durable writes succeed Use `flow/poll` for external async job completion, not generic data paging. ## Fanout And Child Flows Use fanout or child flows when units should be isolated, retried, observed, or installed independently. Keep parent flows focused on orchestration and summary output. Use grouped flow metadata for workspace display, but runtime linkage comes from `flow/call-flow`, `:fanout`, or another orchestration primitive. ## Work Unit Contract Before expanding a loop or fanout, name the unit and keep the parent/child contract compact: - input map - output map - persisted artifacts - idempotency or dedupe key - side effects - retryable failure shape ## References - [Run Concurrency](/docs/reference-run-concurrency) - [Step Fanout](/docs/reference-step-fanout) - [Step Table](/docs/reference-step-table) - [Step Wait](/docs/reference-step-wait) - [Persisted Results And Resources](/docs/guide-persisted-results-and-resources) --- Document: Flow Definition URL: https://flows.breyta.ai/docs/reference-flow-definition.md HTML: https://flows.breyta.ai/docs/reference-flow-definition Last updated: 2026-05-21T16:29:37+02:00 # 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, setup requirements, per-run invocation inputs, callable interfaces, optional schedules, reusable definition surfaces (`:templates`, `:functions`, packaged `:steps`, reusable `:agents`), and finally deterministic `:flow` orchestration. The source format is a Breyta DSL with Clojure/EDN syntax and binding logic, not a normal Clojure program. Use Clojure syntax to declare data, functions, and `flow/step` calls, but keep the mental model as orchestration: side effects belong at step boundaries, workflow body logic should stay deterministic, data must remain serializable, and large/variable outputs should be persisted as resource refs instead of carried inline. ## Fields At A Glance | Field | Required | Purpose | Notes | |---|---|---|---| | `:slug` | Yes | Stable flow identifier | Used by CLI/API commands. | | `:name` | Yes | Human-readable display name | Safe to change without changing slug identity. | | `:concurrency` | Yes | Overlap/version execution policy | Defines overlap rules and version behavior. | | `:flow` | Yes | Deterministic orchestration body | Keep orchestration-focused; move shaping/transforms to top-level `:functions`. | | `:description` | No | Operator/user context | Sanitized Markdown on setup/run pages. | | `:deploy-key-required` | No | Protect release/promotion operations behind a shared deploy key | Stored as flow metadata; when true, guarded release/promotion operations require a valid key. | | `:requires` | No | Setup dependencies | Connections/secrets/setup/worker requirements. Per-run fields belong in `:invocations`. | | `:invocations` | No | Per-run input contracts | Inputs supplied by users, webhooks, CLI, or clients. Use `:default` for normal flows. | | `:interfaces` | No | Callable ingress surfaces | Manual, HTTP, webhook, or MCP surfaces over invocation contracts. | | `:schedules` | No | Time-based automation | Declares cron/preset schedules that call an invocation. New scheduled automation belongs here. | | `:templates` | No | Reusable large static content | Best for request bodies, prompts, queries, notifications. | | `:functions` | No | Reusable Clojure transforms | Call via `flow/step :function` refs from `:flow`. | | `:steps` | No | Flow-local packaged steps | Qualified ids only, for example `:github/open-pr`. | | `:agents` | No | Flow-local named agent definitions | Qualified ids only, for example `:review/security`. Reusable agent configurations invocable from `flow/step` and publishable as tools for other agents. | | `:providers` | No | Flow-local provider definitions | Register custom LLM and files providers under `{:llm [...] :files [...]}`. Referenced via `:provider` in `:llm`, `:agent`, and `:files` steps. | | `:mcp` | No | Flow-level MCP tool adapters | Reuses `:http-api` connection slots with `:backend :mcp`; adapters allowlist server tools and publish selected tools via `:tools {:mcp [...]}`. | | `:mcp-registries` | No | Optional MCP registry metadata | Authoring metadata for known remote MCP endpoints; runtime exposure still requires explicit entries in `:mcp` and step-local `:tools`. | | `:tags` | No | Classification/feature tags | Optional metadata only; does not gate install/run behavior. | ## Final Output Contract The flow definition does not declare a separate output schema today. The implicit contract is: ```text final value returned by :flow = run output ``` That final value is captured as the canonical `flow-output` resource and is also normalized into the user-facing Output page artifact. Treat the last expression of `:flow` as part of the flow's product design, not only as debug data. ## Invocation Adapters `:invocations` is the callable contract. The normal authoring model is one solution flow with one public `:default` invocation. Runtime surfaces adapt into that contract: - manual interfaces provide a human run button/form - schedules provide time-based activation - webhook interfaces provide event intake, auth, replay/idempotency, and generated endpoint delivery - HTTP and MCP interfaces expose the same contract to external clients and tools For new flows, put per-run payload fields in `:invocations {:default ...}` and reference them from an interface or schedule with `:invocation :default`. Use additional invocation ids only when a manual form, webhook payload, schedule payload, or private/internal caller needs a genuinely different input contract. Do not use webhook event config as a generic HTTP API shape, and do not put new manual or schedule entrypoints under the older entrypoint map. New source belongs under `:interfaces` and `:schedules`; old trigger definitions are compatibility source only. `:interfaces` exposes the solution invocation to humans and external systems. Use it when a manual form, another program, coding agent, integration, or webhook provider should call the flow through an explicit surface. ```clojure {:invocations {:default {:inputs [{:name :domain :type :text :label "Domain" :required true}]}} :interfaces {:http [{:id :enrich-company :invocation :default :enabled true :method :post :path "/companies/enrich" :auth :workspace-api-auth}] :webhook [{:id :company-updated :invocation :default :enabled true :event-name "companies.updated" :auth {:type :hmac-sha256 :header "X-Signature" :secret-ref :company-webhook-secret}}] :mcp [{:tool-name "enrich_company" :description "Research and score a company from a domain." :invocation :default :enabled true :auth :workspace-api-auth}] :manual [{:id :run :label "Run enrichment" :invocation :default :enabled true}]} :schedules [{:id :daily-enrichment :label "Daily enrichment" :invocation :default :cron "0 8 * * *" :timezone "UTC"}]} ``` Interfaces are explicit allowlists, not automatic publication of every invocation. A flow should expose one public callable contract for the solution, with at most one manual adapter, one HTTP adapter, one webhook adapter, and one MCP adapter. If you are tempted to expose several unrelated tools from one flow, split the solution into separate flows instead. Runtime access is still gated by the selected installation/live target, setup/bindings, interface enabled state, paid entitlement where applicable, and current workspace/API authentication. When another flow, coding agent, or external client reuses a public flow, the installed HTTP or MCP interface is the preferred composition surface because it applies the installer's setup, auth, billing entitlement, and ownership. MCP clients should connect with a scoped workspace API credential for the installation/interface surface; tool discovery should not expose non-interfaced or unauthorized invocations. Keep auth on the interface adapter, not on the invocation. The same invocation can be called by manual UI, CLI, HTTP, MCP, schedule, or webhook adapters; each adapter owns its own caller authentication. ## Webhook Interfaces Webhook ingress is authored as an interface over an invocation contract: ```clojure {:requires [{:slot :webhook-signing-secret :type :secret :label "Webhook Signing Secret"}] :invocations {:default {:inputs [{:name :order-id :type :text :label "Order ID" :required true}]}} :interfaces {:webhook [{:id :orders-updated :invocation :default :enabled true :event-name "orders.updated" :auth {:type :hmac-sha256 :header "X-Signature" :secret-ref :webhook-signing-secret}}]}} ``` `event-name` controls the generated webhook event name. If omitted, Breyta uses the interface `:id`. During installation/runtime sync, Breyta materializes the webhook interface as the installation-owned webhook runtime endpoint and renders the generated endpoint in setup/CLI surfaces. Legacy webhook definitions are still read and materialized so existing flows continue to run, but new source should use `:interfaces {:webhook [...]}`. Author/consumer CLI loop: ```bash breyta flows push --file ./company-intel.clj breyta flows run company-intel --input '{"domain":"example.com"}' --wait breyta flows interfaces call company-intel enrich-company --input '{"domain":"example.com"}' --wait breyta flows release company-intel breyta flows interfaces list company-intel --target live breyta flows interfaces show company-intel enrich-company --target live breyta flows interfaces curl company-intel enrich-company --target live --input '{"domain":"example.com"}' breyta flows interfaces call company-intel enrich-company --target live --input '{"domain":"example.com"}' --wait breyta flows metrics company-intel --source live ``` For a specific end-user installation: ```bash breyta flows installations interfaces breyta flows interfaces call company-intel enrich-company --installation-id --input '{"domain":"example.com"}' --wait breyta flows metrics company-intel --installation-id ``` Author-owned interface endpoints are flow-source scoped. Draft and live are separate surfaces, so generated endpoints include `draft` or `live` in the path: ```text /api/flows/{flow-slug}/interfaces/draft/{interface-id} /api/flows/{flow-slug}/interfaces/live/{interface-id} ``` Workspace-scoped forms remain available as alternate compatibility URLs: ```text /api/workspaces/{workspace-id}/flows/{flow-slug}/interfaces/draft/{interface-id} /api/workspaces/{workspace-id}/flows/{flow-slug}/interfaces/live/{interface-id} /api/workspaces/{workspace-id}/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id} ``` Installed consumer endpoints are installation scoped: ```text /api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id} ``` HTTP interface calls return the normal run envelope. When a run is accepted and a `workflowId` is available, the response also includes `data.statusUrl`, scoped to the same interface endpoint. Use that URL to poll long-running interfaces without falling back to a broad run lookup. MCP interface `tools/call` requests are optimized for coding-agent and LLM-agent use. The server starts the backing flow run, waits for a terminal state up to a bounded 180-second timeout, and returns the completed flow result in the MCP response `content` and `structuredContent`. Breyta run metadata such as `workflowId`, status, and status URL is attached under `_breyta`. Failed or timed-out runs return a JSON-RPC tool error with the same scoped run metadata. For installed public-flow reuse, point the MCP client at the installation-scoped Streamable HTTP endpoint, authenticate with bearer auth, call `initialize`, then `tools/list`, then `tools/call` with the advertised tool name. Invocation metrics are aggregate entrypoint health signals for authors. They are redacted by design and must not include raw inputs, outputs, headers, tokens, secrets, or binding values. Metrics include `interfaceScope`, so author-owned draft/live calls stay separate from installed consumer calls to the same interface id. Recommended output shapes: - readable report: return `{:breyta.viewer/kind :markdown ...}` - report with real resources: use Markdown plus fenced `breyta-resource` blocks - durable grid: persist a table and return or embed it as a table viewer - media: persist a blob and return or embed an image/audio/video viewer - structured data as the product: return a `:raw` viewer Do not expose internal `res://` refs, raw debug maps, trace ids, or automation payloads in public/end-user outputs unless that structured payload is explicitly the product output. See [Output Artifacts](/docs/guide-output-artifacts) for the full viewer and Markdown resource embed contract. ## Recommended authoring order Default pattern: 1. `:requires` - declare connections, secrets, setup form inputs, and worker/runtime dependencies - decide what the flow needs before writing behavior - inventory and test existing workspace connections before inventing new setup - choose stable slot names around business capability (`:github-api`, `:crm`, `:llm`, `:slack`) rather than transient provider names - prefer reusing known-good connections and then shaping packaged `:steps` / `:agents` around those slots - if a packaged tool wraps a broad connection-backed surface, add flow-local permission hints here before publishing it to an agent 2. `:invocations` - declare per-run fields that a user, CLI command, interface, schedule, or client provides - use `:default` for a single run form - point manual interfaces at the contract with `:invocation :default` - put webhook ingress under `:interfaces :webhook` and point it at the same contract with `:invocation :default` 3. `:interfaces` - declare manual, HTTP, webhook, and MCP call surfaces over invocations - use `:manual` for Resource UI launch forms and "run now" actions - use `:webhook` for inbound provider events with explicit auth - use `:http` and `:mcp` for programmatic clients and coding agents 4. `:schedules` - declare cron/preset automation over invocations - use stable schedule ids because setup overrides and CLI toggles address schedules by id 5. `:templates` - move prompts, request bodies, SQL, notification content, and other large static text here 6. `:functions` - put shaping, normalization, preparation, and projection logic here 7. `:steps` - package heavy built-in step configs behind a narrower input/output contract - especially for broad surfaces like `:http`, `:db`, `:notify`, `:function`, and `:agent` 8. `:agents` - define reusable named agent roles here when the behavior is agent-shaped - capture objective, instructions, memory, cost/evaluate/trace, and delegation config once 9. `:mcp` when using remote MCP tools - bind the remote endpoint as a normal `:http-api` requirement with `:backend :mcp` - define a top-level MCP adapter with `:connection`, optional `:path`, `:transport :streamable-http`, and explicit `:allow-tools` - select adapter ids or individual tool refs from `:llm` / `:agent` steps via `:tools {:mcp [...]}` 10. `:flow` - wire those surfaces together in deterministic orchestration - keep orchestration focused on sequencing, branching, waits, persistence, and summaries This is the preferred pattern for new flows. If you find yourself writing large inline prompts, repeated transforms, or raw heavy built-in step configs directly inside `:flow`, move that work up into one of the reusable top-level surfaces. For agentic reviewer/fixer/coordinator flows, the default pattern should be: - `:files` for code/resource state - packaged `:steps` for heavy external or policy-shaped operations - named `:agents` for reviewer/fixer/coordinator roles - orchestration last For installable flows, the authoring order matters more because it determines the author/installer boundary: - **`:requires`** defines what the installer configures. Use `:provided-by :author` for connections you want to absorb (LLM keys, internal APIs). Use installer-provided for connections the installer owns (their GitHub repo, their database). - **`:steps`** and **`:agents`** reference connection slots, not IDs. The slot name resolves through `:requires` bindings at runtime, so it works in both the author's workspace and any installation. - **`:memory`**, **`:cost`**, **`:trace`**, **`:evaluate`** are internal to the flow — the installer never sees these resources. - **`:output-persist`** writes to the installer's workspace — this is the product output they access. See the [Installable Agent Flows](/docs/guide-installations) section for a complete checklist and example. ## MCP Tool Adapters Remote MCP servers are modeled as HTTP API connections. Do not declare a separate MCP connection type; use `:type :http-api` and `:backend :mcp` so connection auth, base URL validation, SSRF protection, and installation binding stay on the existing connection path. Example: ```clojure {:requires [{:slot :linear-mcp :type :http-api :label "Linear MCP" :backend :mcp :base-url "https://mcp.linear.app" :auth {:type :bearer} :provided-by :installer}] :mcp [{:id :linear/issues :connection :linear-mcp :transport :streamable-http :allow-tools ["list_issues" "get_issue"] :deny-tools ["delete_issue"] :tool-prefix "linear" :timeout-ms 15000 :max-result-bytes 200000 :tools [{:name "list_issues" :description "List Linear issues." :input-schema {:type "object" :properties {:team_id {:type "string"}} :required ["team_id"]}}]}] :flow '(flow/step :agent :triage {:connection :ai :objective "Find the most relevant issue." :tools {:mode :execute :mcp [:linear/issues]}})} ``` Only explicitly selected tools are published to the model. Selecting an adapter such as `:linear/issues` requires `:allow-tools` and publishes only those allowed tools; selecting `:linear/list_issues` publishes just that named tool. MCP calls run through the same agentic tool loop as built-in, packaged-step, and agent-to-agent tools. Connection slots are required unless marked optional. Use `:optional true` for new definitions; `:required false` is accepted as the inverse setup alias for connection slots. ## Manual Interfaces For manual launch, declare one `:interfaces :manual` entry that points at an invocation. Its `:label` is 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 no manual interface label is set, the fallback copy is `Run now`. Older manual entrypoint definitions remain accepted for existing flows, but new source should use a manual interface. Manual interface input fields are declared on the referenced invocation, not on the manual interface entry itself. For author-run forms, put field maps under `:invocations {:default {:inputs [...]}}`, then point `:interfaces {:manual [...]}` at that invocation. Manual forms support scalar fields, direct file upload fields, and resource-picker fields. See [Invocation Inputs](#invocation-inputs) for the canonical key list, supported `:type` values, and upload examples. Example: ```clojure {:invocations {:default {:label "Ask project question" :inputs [{:name :question :type :text :label "Question" :required true}]}} :interfaces {:manual [{:id :ask-project-question :label "Ask project question" :invocation :default :enabled true}]}} ``` ## Schedules Scheduled automation is authored as top-level `:schedules` entries that point at invocations. Public installable schedule setup is available for every schedule; no separate entrypoint source is required. The authored schedule is the default schedule. Author setup overrides, if saved, become the inherited default for installers. Each installer can then save their own installation-scoped schedule settings and independently turn the schedule on or off. ```clojure {:schedules [{:id :weekly-review :label "Scheduled security review" :invocation :default :enabled false :cron "0 9 * * MON" :timezone "UTC"}]} ``` Current behavior: | Field | Behavior | |---|---| | Schedules | Render schedule controls inside setup. | | Saved choices | Stored as installation schedule settings. New installed schedules start disabled until enabled. | | Author setup | Author overrides use the same controls. Reset 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 | Saved/default zones win; detected zones fill gaps. | | Source schedule config | Setup does not rewrite authored `:cron` or `:timezone`; schedule sync/runtime consumes saved schedule settings for installer-specific schedules. | | Automatic execution state | Installed flows use per-schedule and per-interface enablement. | | Webhook interface setup | Setup exposes webhook interfaces as individual switches. | Schedule settings can also be changed through the CLI. Authors use `breyta flows configure --schedules '{...}'`; installers use `breyta flows installations configure --schedules '{...}'`. Use the authored schedule id as the schedule key. `--schedule-enable ` and `--schedule-disable ` toggle one schedule without writing the full JSON shape. `--schedule-reset ` removes a saved schedule override so the schedule inherits the authored flow default again. For a paid public flow, keep a manual run path available while validating the schedule runtime path for installer-specific timezone, pause/resume, and failure visibility. ## Deploy-Key Guard Use `:deploy-key-required` to gate release operations for a flow: ```clojure {: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: | Aspect | `draft` target | `live` target | |---|---|---| | Definition source | Latest pushed working copy in workspace. | Installed released version. | | How target changes | `breyta flows push --file ...` | `breyta flows release ` or `breyta flows promote ` | | `:requires` resolution | `breyta flows configure ...` values. | `breyta flows configure --target live ...` or installation-specific configuration values. | | Webhook interface endpoint surface | Flow-source scoped endpoints with `draft` in the path. | Flow-source scoped author testing endpoints with `live` in the path, plus installation-scoped consumer endpoints. | | Typical verification run | `breyta flows run --wait` | `breyta flows run --target live --wait` | | Best use | Authoring iteration and fast feedback. | Stable consumer/runtime behavior. | `flows push` does not modify `live`; it only updates `draft`. ## Connection-Scoped Packaged Tool Permissions Connection requirements can carry flow-local runtime permission hints for packaged tools. This is mainly useful when a packaged `:steps` definition wraps `:http` and should only be allowed to call a narrow part of a broader API connection. These permissions live on the requirement slot, not on the workspace connection record: ```clojure {:requires [{:slot :github-api :type :http-api :label "GitHub API" :auth {:type :none} :base-url "https://api.github.com" :config {:tool-permissions {:http {:mode :read-only :allowed-hosts ["api.github.com"] :allowed-path-prefixes ["/repos/breyta/breyta"]}}}}]} ``` The current generic HTTP permission hints are: - `:mode` - `:allowed-methods` - `:allowed-hosts` - `:allowed-path-prefixes` - `:allowed-url-prefixes` Runtime enforces these guards when the packaged step executes, whether it is invoked directly from `flow/step` or called as an agent tool through `:tools {:steps [...]}`. ## Worker Requirements Use `{:kind :worker ...}` inside `:requires` when install behavior depends on external workers instead of installer-entered setup values. Examples: ```clojure {: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"}]} ``` ```clojure {: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"}]} ``` Modeling rules: - Use `:requires` for both setup inputs and external worker/runtime dependencies. - Use `:provided-by :author` when the flow author supplies the worker/runtime. - Use `:provided-by :installer` when the installer or operator must run the worker/runtime. - A flow can declare more than one worker requirement when different job types or worker capabilities are needed. - Prefer `:job-types` in authored worker requirements. `:job-type` is accepted as a single-type alias when only one job type is needed. ## Setup Forms And Invocation Inputs Use `:requires` for setup values saved on the configured target. Use `:invocations` for values supplied each time the flow is run. Setup form example: ```clojure {:requires [{:kind :form :label "Setup" :fields [{:key :region :label "Region" :field-type :select :required true :options ["EU" "US"]}]}]} ``` Per-run input example: ```clojure {:invocations {:default {:label "Run input" :inputs [{:name :question :label "Question" :type :text :required true}]}} :interfaces {:manual [{:id :ask :label "Ask" :invocation :default :enabled true}]} :flow '(let [input (flow/input)] {:region (:region input) :question (:question input)})} ``` The flow sees setup values and invocation inputs together in `flow/input`. Setup values persist until reconfigured. Invocation inputs apply only to the current run. ### Setup Form Requirements Use `{:kind :form ...}` inside `:requires` when the flow needs structured setup input before it can run. Top-level setup form keys: | Key | Required | Meaning | |---|---|---| | `:kind` | Yes | Must be `:form`. | | `:fields` | Yes | One field map or a vector of field maps. | | `:label` | No | Group label shown in setup UI. | | `:provided-by` | No | `:installer` (default) or `:author`. Author-provided requirements are not shown to the installer. | | `:collect` | No | Deprecated for new source. Omit it for setup; `:setup` remains the default for old examples. | Setup field keys: | Key | Required | Meaning | |---|---|---| | `:key`, `:name`, or `:id` | Yes | Stable submitted field id. | | `:label` | No | Display label in UI. | | `:field-type` | No | Input type. Defaults to `:text`. | | `:required` | No | When true, the field must be filled before save/run. | | `:placeholder` | No | Helper text shown in the input UI. | | `:default` | No | Default value shown when the form first loads. | | `:options` | For `:select` | Static allowed values. | | `:options-source` | For `:select` | Dynamic option source metadata. Use when options come from platform services, such as items visible through a bound connection. | | `:multiple` | For `:resource` | Allow selecting more than one resource. | | `:resource-types` | For `:resource` | Optional resource type filter such as `[:file :result]`. | | `:accept` | For `:resource` | Optional MIME filter such as `["application/pdf" "text/plain" "text/*"]`. | | `:slot` | For `:resource` | Optional local blob-storage slot such as `:archive`. | | `:storage-backend` | For `:resource` | Optional explicit storage backend filter such as `:gcs`. Usually inferred from `:slot` when present. | | `:storage-root` | For `:resource` | Optional explicit storage root such as `"reports/acme"`. Usually inferred from `:slot` when present. | | `:path-prefix` | For `:resource` | Optional relative path prefix under the effective storage root, such as `"exports/2026"`. | | `:source` | For `:resource` | Legacy picker routing override. Omit in normal flow authoring. | Supported setup `:field-type` values: | `:field-type` | UI behavior | |---|---| | `:text` | Single-line text input | | `:textarea` | Multi-line text box | | `:select` | Dropdown | | `:boolean` | Checkbox/toggle | | `:number` | Numeric input | | `:date` | Date picker | | `:time` | Time picker | | `:datetime` | Date-time picker | ### Invocation Inputs Declare fields under `:invocations` when the value is supplied per run. Run input validation normalizes declared `:number` fields before the flow starts, so function steps should receive numbers for those fields. Invocation input keys: | Key | Required | Meaning | |---|---|---| | `:name`, `:key`, or `:id` | Yes | Stable submitted field id. The flow reads this key from `flow/input`. | | `:type` | No | Input type. Defaults to text-like behavior when omitted. | | `:label` | No | Display label in UI. | | `:description` | No | Help text for the input. | | `:required` | No | When true, the field must be supplied before the run starts. | | `:placeholder` | No | Helper text shown in the input UI. | | `:default` | No | Default value shown when the run form first loads. | | `:options` | For `:select` | Static allowed values. | | `:options-source` | For `:select` | Dynamic option source metadata. Use when options come from platform services, such as items visible through a bound connection. | | `:multiple` | For `:file`, `:blob-ref`, or `:resource` | Allow multiple selected/uploaded values. | | `:resource-types` | For `:resource` | Optional resource type filter such as `[:file :result]`. | | `:accept` | For `:file`, `:blob-ref`, or `:resource` | Optional MIME filter such as `["application/pdf" "text/plain" "text/*"]`. | | `:slot` | For `:resource` | Optional local blob-storage slot such as `:archive`. | | `:storage-backend` | For `:resource` | Optional explicit storage backend filter such as `:gcs`. Usually inferred from `:slot` when present. | | `:storage-root` | For `:resource` | Optional explicit storage root such as `"reports/acme"`. Usually inferred from `:slot` when present. | | `:path-prefix` | For `:resource` | Optional relative path prefix under the effective storage root, such as `"exports/2026"`. | Dynamic select option sources: - `:options` remains the static select option list. - `:options-source` declares platform-owned option discovery for cases where choices depend on an installation binding or setup state. - The first supported source type is `:connection-items`, which reads non-secret cached item metadata for the selected connection binding. - Option hydration and validation read Breyta's connection-owned item cache. It does not run arbitrary author code or make provider API calls from the form renderer or setup/run save path. - Setup and run submissions fail closed when the backing connection is missing, the cache is empty, the selected item is disabled, or the submitted value is not in the cached options. - This validation is scoped to the declared connection-backed field. It is not a general connection health check, and connection health/readiness checks remain best-effort operational signals. - Declare either `:slot` or `:connection` as the local connection slot reference. Prefer `:slot`; `:connection` is accepted as a compatibility alias. Do not set both unless they refer to the same slot. - `:item-type` is a connector-defined category such as `"project"`, `"channel"`, `"account"`, `"repository"`, or a provider-qualified variant such as `"linear/project"` or `"slack/channel"`. - `:item-type` is required and is matched as a string, so provider-qualified values such as `"github/repository"` keep their slash. - `:value-field`, `:label-field`, and `:description-field` optionally map provider item payload fields into dropdown values. When omitted, the renderer falls back to `value`/`id` for the submitted value, `label` for display text, and `description` for helper text. - The submitted value is still a normal scalar form value; the option source is a UX and validation hint, not a runtime connection handle. - Runtime still validates access when the flow uses the selected value, because connector authorization can change after picker metadata is cached. Example: ```clojure {:requires [{:slot :work-system :type :http-api :label "Work system" :provided-by :installer}] :invocations {:default {:inputs [{:name :target-project :label "Project" :type :select :required true :options-source {:type :connection-items :slot :work-system :item-type "project" :value-field "id" :label-field "name"}}]}}} ``` Supported invocation `:type` values: | `:type` | UI behavior | |---|---| | `:text` or `:string` | Single-line text input | | `:textarea` | Multi-line text box | | `:select` | Dropdown | | `:boolean` or `:checkbox` | Checkbox/toggle | | `:number` | Numeric input | | `:date` | Date picker | | `:time` | Time picker | | `:datetime` | Date-time picker | | `:file`, `:blob`, or `:blob-ref` | Upload file input | | `:resource` | Select existing workspace resources or upload local files | | `:secret`, `:password`, `:email` | Text inputs with specialized semantics/UI where supported | Manual interface upload inputs quick reference: - use `:file`, `:blob`, or `:blob-ref` when the run form should accept a new local upload for that run - use `:resource` when the run form should select existing workspace resources and optionally allow local uploads into that picker - use `:multiple true` when the submitted value should be a collection - use `:accept [...]` to filter by MIME type for both browser file selection and resource picker results - uploaded files are passed to the flow as blob/resource reference maps, not inline bytes; downstream steps should read or pass those refs explicitly Direct file upload example: ```clojure {:invocations {:default {:label "Upload receipts" :inputs [{:name :receipts :label "Receipts" :type :file :required true :multiple true :accept ["application/pdf" "image/*"]}]}} :interfaces {:manual [{:id :upload-receipts :label "Upload receipts" :invocation :default :enabled true}]}} ``` The flow reads the uploaded refs from `flow/input`: ```clojure '(let [{:keys [receipts]} (flow/input)] {:receipt-count (count receipts) :receipt-refs receipts}) ``` Resource input example: ```clojure {:requires [{:slot :archive :type :blob-storage :label "Archive storage" :config {:prefix {:default "reports" :label "Folder prefix" :placeholder "reports/customer-a"}}}] :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 :ask :label "Ask" :invocation :default :enabled true}]}} ``` Resource input expectations: - selected values are passed as resource references, not full file contents - use `:multiple true` when the flow expects a collection of resources - local uploads are saved as workspace `:file` resources and auto-selected for the current run - `: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 - persisted table resources are `:result` resources with content type `application/vnd.breyta.table+json` - use `:resource-types [:result]` and optionally `:accept ["application/vnd.breyta.table+json"]` when the picker should show only table resources - use `: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 Table-resource input example: ```clojure {:invocations {:default {:inputs [{:name :source-table :label "Source table" :type :resource :resource-types [:result] :accept ["application/vnd.breyta.table+json"]}]}}} ``` Text-focused resource allowlist: ```clojure {:name :resources :label "Text resources" :type :resource :required true :multiple true :accept ["text/*" "application/json" "application/xml" "application/edn"]} ``` Example input seen by the flow: ```clojure {: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"}]} ``` ### Blob Storage Slots And Resource Pickers Blob-storage authoring lives in `:requires` because the installer configures the storage binding once. Per-run pickers live in `:invocations` because the selected resource changes on each run. ```clojure {:requires [{:slot :archive :type :blob-storage :label "Archive storage" :config {:prefix {:default "reports"}}}] :invocations {:default {:inputs [{:name :report :label "Archived report" :type :resource :required true :accept ["application/pdf"] :slot :archive}]}}} ``` 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//reports` when no 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` Optional setup hint for cross-flow sharing: ```clojure {: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. ### Deprecated Run Form Shapes These forms are still accepted so existing flows keep working, but do not use them in new source. - Run forms previously collected from requirements should move their fields to `:invocations`. - Manual launch fields should move to `:invocations` and be exposed through `:interfaces :manual`. - Time automation should move to top-level `:schedules`. - Webhook ingress should move payload fields to `:invocations` and expose the webhook through `:interfaces :webhook`. When a legacy flow has no explicit `:invocations`, Breyta derives a default invocation from older run field shapes. Legacy manual, schedule, and webhook definitions remain accepted, but they are not the preferred canonical invocation/interface/schedule source. Adding an explicit `:invocations` map makes that map the source of truth, and adding `:interfaces {:webhook [...]}` makes the interface map the source of webhook ingress truth. ## Supported Orchestration Constructs Inside `:flow`, supported non-step forms are: | Category | Constructs | Typical Use | |---|---|---| | Binding / sequencing | `let`, `do` | Build deterministic orchestration pipelines. | | Branching | `if`, `if-not`, `when`, `when-not`, `cond`, `case` | Route execution by state or decision flags. | | Iteration | `for`, `doseq`, `loop`, `recur` | Deterministic fan/loop orchestration. | | Child flow orchestration | `flow/call-flow` | Delegate larger branches to child flows. | | Polling helper | `flow/poll` | Bounded 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` `:fanout` is a step, not a non-step orchestration form. Use `flow/step :fanout` when you need bounded child-workflow batch spawn/collect, and see [Step Fanout](/docs/reference-step-fanout) for its guardrails and result shape. `:job` is also a step, not a non-step orchestration form. Use `flow/step :job` when you need to submit, read, or await external jobs in the Breyta jobs control plane, and see [Step Job](/docs/reference-step-job) for the step contract. ## Common Orchestration Shape Use one orchestration layer in `:flow`, then delegate: - shaping/normalization to `:functions` - large static bodies/prompts/queries to `:templates` - heavy built-in step configs behind simplified packaged `:steps` - reusable agent roles and delegation policy to top-level `:agents` - heavy outputs to `:persist` - external worker execution to `flow/step :job` - larger branches to child flows via `flow/call-flow` - bounded child-workflow batch spawn/collect to `:fanout` with `{:type :call-flow ...}` items ```clojure {:functions [{:id :prepare :language :clojure :code "(fn [input] {:order-id (:order-id input) :lookup-key (str \"orders:\" (:order-id input))})"}] :steps [{:id :billing/fetch-order :type :http :description "Fetch an order from the billing API." :input-schema [:map [:order-id :string]] :defaults {:connection :orders-api :template :fetch-order} :prepare :prepare}] :flow '(let [input (flow/input) order (flow/step :billing/fetch-order :fetch-order {:order-id (:order-id input) :persist {:type :blob}}) route (flow/call-flow :order-routing {:order-ref (:uri order) :lookup-key (str "orders:" (:order-id input))}) 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 (:uri order) :route route :result result})} ``` ## Packaged Steps Top-level `:steps` let you define flow-local packaged step wrappers around heavy built-in step configs. Rules: - packaged step ids must be qualified keywords such as `:github/open-pr` - unqualified keywords remain reserved for built-in step families like `:http`, `:files`, and `:table` - packaged steps wrap one underlying built-in step - use top-level `:functions` for `:prepare` and `:project-result` helpers Canonical shape: ```clojure {:steps [{:id :github/open-pr :type :http :description "Open a draft GitHub pull request." :input-schema [:map [:owner :string] [:repo :string] [:title :string] [:head :string] [:base :string]] :output-schema [:map [:url :string]] :defaults {:connection :github-api :template :open-pr-template} :prepare :prepare-open-pr :project-result :project-open-pr :tool {:name "github_open_pr" :description "Open a draft GitHub pull request."}}]} ``` Direct invocation uses the packaged step id directly in `flow/step`: ```clojure '(flow/step :github/open-pr :open-pr {:owner "breyta" :repo "breyta" :title "Fix null handling" :head "task/fix-null" :base "main"}) ``` How defaults and helpers are resolved: - `:defaults` merge into the wrapped built-in step config - `:prepare` and `:project-result` resolve from top-level `:functions` - flow-local `:templates` and default `:connection` values are resolved by the wrapped built-in step at runtime - the same resolution applies when a packaged step is published as an agent tool through `:tools {:steps [...]}`; the tool call still runs through the wrapped built-in step with those packaged defaults applied - common outer step controls like `:retry`, `:timeout`, `:persist`, and `:on-error` still belong on the outer `flow/step` call, not in the packaged step definition When one parent needs several sibling child workflows, keep the batch bounded and use `:fanout` with `:call-flow` items only. Legacy non-child fanout remains available for compatibility, but it runs on the sequential compatibility path. ## Agent Definitions Top-level `:agents` define named, reusable agent configurations. Each agent definition declares its objective, tools, connection, iteration budget, and optional memory in one place. Agent definition fields: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:id` | qualified keyword | Yes | e.g. `:review/security`, `:fix/single-area` | | `:description` | string | Yes | Human-readable description (max 4000 chars) | | `:objective` | string | Yes | The agent's objective template | | `:instructions` | string | No | Additional guidance appended to system prompt | | `:input-schema` | schema | No | Validates invocation input before execution | | `:output-schema` | schema | No | Validates agent output (warning only) | | `:connection` | keyword/string | No | Default LLM connection slot | | `:model` | string | No | Default model | | `:available-steps` | vector | No | Built-in step tools (`:files`, `:table`, `:search` only) | | `:tools` | map | No | Full tools config including `:steps [...]` and `:agents [...]` | | `:max-iterations` | int | No | Iteration cap; default `20`, max `100` | | `:max-tool-calls` | int | No | Total executed tool-call cap; default `100`, max `1000` | | `:max-repeated-tool-calls` | int | No | Optional cap for identical tool name+arguments calls; unset by default, max `100` | | `:memory` | map | No | Memory table config for cross-run context | | `:output` | map | No | Output format (e.g. `{:format :json}`) | | `:tool` | map | No | Tool publication metadata (`:name`, `:description`) | Rules: - agent ids must be qualified keywords such as `:review/security` - each agent definition IS an `:agent` step config — no wrapping needed - invocation input is validated against `:input-schema` and becomes `:inputs` - agents can be published as tools for other agents via `:tools {:agents [...]}` Canonical shape: ```clojure {:agents [{:id :review/security :description "Scan code for security issues." :objective "Scan the selected area for OWASP top 10 issues." :instructions "Focus on injection, auth bypass, and data leaks." :connection :ai :model "gpt-5-mini" :available-steps [:files :table] :max-iterations 20 :max-tool-calls 80 :max-repeated-tool-calls 3 :input-schema [:map [:area :string] [:repo-tree :any]] :output {:format :json} :output-schema [:map [:findings [:vector :string]]] :tool {:name "security_scan" :description "Run a security scan on a code area."}}] :flow '(let [result (flow/step :review/security :scan-auth {:area "auth" :repo-tree repo-tree})] (:findings result))} ``` Invocation: - everything in the invocation config that isn't an overlay key becomes agent `:inputs` - overlay keys (`:connection`, `:model`, `:max-iterations`, `:max-tool-calls`, `:max-repeated-tool-calls`, `:memory`) override defaults - the agent runs a full agentic loop with its own iteration budget and tools Agent-to-agent delegation: ```clojure '(flow/step :agent :coordinator {:connection :ai :objective "Review the repo, then delegate scans." :tools {:agents [:review/security] :allowed ["files"]} :max-iterations 10}) ``` The coordinator sees `security_scan` as a tool. When called, the sub-agent runs a nested agentic loop with its own budget, and the projected result returns to the coordinator as tool output. ## Providers Top-level `:providers` registers named provider definitions for step families. Use `:providers {:llm [...]}` for custom LLM endpoints that `:llm` and `:agent` steps can use via `:provider`. Use `:providers {:files [...]}` for custom repository/source providers that `:files` steps can use via `:provider`. Only the `:llm` and `:files` provider families are accepted; unknown family keys are rejected during flow validation so typos do not silently disappear. ### LLM family-based providers Reuse an existing provider family with custom defaults: ```clojure {:providers {:llm [{:id :fireworks-deepseek-r1 :family :chat-completions-compatible :defaults {:base-url "https://api.fireworks.ai/inference/v1" :model "accounts/fireworks/models/deepseek-r1"}} {:id :local-ollama :family :chat-completions-compatible :defaults {:base-url "http://localhost:11434/v1" :model "llama3.1"}}]} :flow '(flow/step :llm :summarize {:connection :ai :provider :fireworks-deepseek-r1 :prompt "Summarize this document."})} ``` Supported families: `:openai` (Responses API), `:deepseek` (DeepSeek Chat Completions with agentic tool-loop support), `:chat-completions-compatible` (generic Chat Completions), `:openai-compatible` (legacy alias for `:chat-completions-compatible`), `:openrouter` (OpenRouter Chat Completions), `:anthropic`, `:bedrock` (AWS Bedrock Runtime for Anthropic Claude with SigV4), `:google`, and `:custom`. The provider inherits all capabilities from its family. `:defaults` are merged under the step config — the step can still override `:model`, `:temperature`, etc. The built-in `:chat-completions-compatible` family intentionally does not declare `:agentic-tool-loop` by default. It covers endpoints that share the Chat Completions request/response shape, but that does not guarantee multi-turn tool-loop behavior. If you have verified that a Chat Completions-like endpoint can replay assistant `tool_calls`, accept subsequent `role=tool` messages, and round-trip tool call ids for the chosen model, opt in explicitly: ```clojure {:providers {:llm [{:id :agentic-compatible-llm :family :chat-completions-compatible :defaults {:base-url "https://llm.example.com/v1" :model "provider/model"} :capabilities #{:tool-calling :structured-output :agentic-tool-loop}}]}} ``` This capability override is author-owned compatibility, not a platform guarantee for every model behind that endpoint. Do not use a custom provider id that matches a built-in provider id such as `:deepseek`, unless you intentionally want the flow-local provider to shadow the built-in provider during that flow execution. ### Custom LLM Providers For endpoints that don't speak a standard API, route calls through a packaged step: ```clojure {:providers {:llm [{:id :internal-llm :family :custom :call-step :internal/call-llm}]} :steps [{:id :internal/call-llm :type :http :description "Call internal LLM API" :input-schema [:map [:messages [:vector :any]]] :defaults {:connection :internal-api :method :post :path "/v1/completions"} :prepare :prepare-llm-request :project-result :project-llm-response}] :functions [{:id :prepare-llm-request :language :clojure :code "(fn [{:keys [messages model]}] {:json {:messages messages :model (or model \"default\")} :response-as :json})"} {:id :project-llm-response :language :clojure :code "(fn [result] {:success true :content (get-in result [:body :choices 0 :message :content]) :usage {:prompt-tokens (get-in result [:body :usage :prompt_tokens]) :completion-tokens (get-in result [:body :usage :completion_tokens]) :total-tokens (get-in result [:body :usage :total_tokens])}})"}]} ``` The `:project-result` function must return the canonical result envelope: `{:success true :content "..." :usage {:prompt-tokens N ...}}`. ### Custom Files Providers Custom files providers route repository operations through packaged steps: ```clojure {:providers {:files [{:id :gitlab :display-name "GitLab" :operations {:load-source-entries :gitlab/load-source :publish-changes :gitlab/publish :open-change-request :gitlab/open-change-request}}]} :steps [{:id :gitlab/load-source :type :http :description "Load GitLab repository entries" :input-schema [:map [:repo :string]] :defaults {:connection :gitlab-api :method :get :path "/projects/{{repo}}/repository/tree"}} {:id :gitlab/publish :type :http :description "Publish GitLab branch changes" :input-schema [:map [:repository :string] [:branch :string]] :defaults {:connection :gitlab-api :method :post :path "/projects/{{repository}}/repository/commits"}} {:id :gitlab/open-change-request :type :http :description "Open GitLab merge request" :input-schema [:map [:repository :string] [:branch :string] [:title :string]] :defaults {:connection :gitlab-api :method :post :path "/projects/{{repository}}/merge_requests"}}]} ``` ### LLM provider definition fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:id` | keyword | Yes | Used as `:provider` value in step config | | `:family` | keyword | Yes | `:openai`, `:deepseek`, `:chat-completions-compatible`, `:openai-compatible` legacy alias, `:openrouter`, `:anthropic`, `:google`, or `:custom` | | `:defaults` | map | No | Merged under step config (base-url, model, headers) | | `:call-step` | keyword | For `:custom` | Qualified packaged step id | | `:display-name` | string | No | Human-readable name | | `:capabilities` | set | No | Overrides family capabilities if set; use explicit `:agentic-tool-loop` only for endpoints/models verified to support multi-turn tool replay | ### Files provider definition fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:id` | keyword | Yes | Used as `:provider` value in `:files` step config | | `:display-name` | string | No | Human-readable provider name | | `:operations` | map | Yes | Maps supported files operations to qualified packaged step ids | | `:operations :load-source-entries` | keyword | Yes | Packaged step used to load source/repository entries | | `:operations :publish-changes` | keyword | Yes | Packaged step used to publish a changeset | | `:operations :open-change-request` | keyword | Yes | Packaged step used to open a pull/merge/change request | ### Connection handling The provider definition does not replace connection bindings. The `:connection` on the `:llm` step still resolves through `:requires` bindings. The provider only determines the API format and endpoint — the connection provides the credentials. For installable flows, use `:provided-by :author` on the connection when the author wants to absorb LLM costs, regardless of which provider is configured. ## Local Source Includes When you author a flow locally for `breyta flows push --file ...`, the CLI can expand explicit local include forms before upload: ```clojure {:templates [#flow/include "flow-assets/templates/security-reviewer.edn"] :functions [#flow/include "flow-assets/functions/normalize-config.edn"] :steps [#flow/include "flow-assets/steps/github-open-pr.edn"] :flow '(let [review-schema #flow/include "flow-assets/review-schema.edn"] review-schema)} ``` Rules: - `#flow/include "relative/path"` resolves relative to the flow source file being pushed. - Expansion happens client-side in the CLI before `flows.put_draft`; the server still receives a normal `flowLiteral`. - Included files can include other files recursively through the same form. - Cycles fail the push. - The authored source file is not rewritten with inlined content during push. Recommended use: - large `:templates` - large `:functions` - reusable packaged `:steps` - adjacent JSON-schema / EDN config fragments used in `:flow` This is an authoring convenience for local source files. Pulled flow source still materializes the resolved content rather than reconstructing your local include tree. ### `:persist` Path Modes Use the two blob persist modes like this: - `:persist {:type :blob ...}` without `:slot` writes under the runtime-managed base `workspaces//persist////...` - `:persist {:type :blob :slot :archive ...}` writes under the installer-bound storage base `workspaces//storage//...` - 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: ```clojure {: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 (:uri report-a) :slot-managed (:uri report-b)})} ``` With `input.customer-id = "cust-77"`: - runtime-managed path: `workspaces//persist////exports/cust-77/summary.pdf` - slot-managed path with root `reports/acme`: `workspaces//storage/reports/acme/exports/cust-77/summary.pdf` ## Built-In Sandbox Helpers These functions are available directly in `:flow` code and `:functions` without requiring imports: | Helper | Description | | --- | --- | | `(json-parse s)` | Parse JSON string → keyword map, returns `nil` on invalid input | | `(json-parse! s)` | Parse JSON string → keyword map, throws on invalid input | | `(json-emit value)` | Serialize Clojure data → JSON string | | `(re-find pattern s)` | First regex match (pattern can be string or regex) | | `(re-matches pattern s)` | Full-string regex match | | `(re-seq pattern s)` | All matches (bounded by sandbox limit) | | `(group-by f coll)` | Group collection by function (bounded) | | `(frequencies coll)` | Count occurrences (bounded) | | `(present? value)` | Not nil, not blank, not empty | | `(blank? value)` | Nil, empty string, or empty collection | Also available: `clojure.string/*` (via `str/`), `clojure.set/*` (via `set/`), and `breyta.sandbox/*` for base64, hex, SHA-256, UUID, instant parsing, and URL encoding. Common pattern with agent output: ```clojure '(let [result (flow/step :agent :review {:objective "..." :output {:format :json}}) findings (or (:content result) (json-parse (:content result)))] (group-by :severity (:findings findings))) ``` ## 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](/docs/reference-limits-and-recovery) ## Related - [Flow Basics](/docs/build-flow-basics) - [Flow Authoring](/docs/build-flow-authoring) - [Run Concurrency](/docs/reference-run-concurrency) - [Orchestration Constructs](/docs/reference-orchestration-constructs) - [Jobs Control Plane](/docs/reference-flow-jobs) - [flow/poll](/docs/reference-flow-poll) - [Templates](/docs/reference-templates) - [Functions](/docs/reference-functions) - [Limits And Recovery](/docs/reference-limits-and-recovery) --- Document: Runtime Data Shapes URL: https://flows.breyta.ai/docs/reference-runtime-data-shapes.md HTML: https://flows.breyta.ai/docs/reference-runtime-data-shapes Last updated: 2026-05-15T14:59:18+02:00 # Runtime Data Shapes Use this page when authoring function steps, passing persisted refs, or reading CLI/API output without adding redundant parser code. ## Default Assumption Breyta flow values are usually Clojure maps and vectors already. Prefer map access over defensive string parsing: ```clojure (let [customer-id (get-in input [:invoice :customer-id]) rows (:rows table-result)] {:customer-id customer-id :row-count (count rows)}) ``` Only parse JSON when the value is actually a JSON string or bytes. ## Common Runtime Shapes | Source | Shape | | --- | --- | | `flow/input` | run input map declared by `:invocations` | | step result | Clojure map, vector, scalar, or persisted ref map | | `:http` inline result | map with status, headers, and body fields when not persisted | | persisted blob result | compact ref map with `:uri` or `:resource-uri` plus metadata | | persisted table result | table ref plus preview/count metadata | | CLI command result | JSON envelope with `ok`, `data`, `meta`, and optional `links` / `error` | ## Step Run Result Preview `breyta steps run` is optimized for authoring loops. Default output omits the full `data.result` and returns `data.resultPreview` instead: ```json { "data": { "resultPreview": { "binding": "make-output", "format": "clojure-value-preview", "value": "{:rows [{:id \"row-1\", :score 0.92}], :summary \"ready\"}", "truncated": false } } } ``` Read `resultPreview.value` as the value shape you would bind from `(flow/step ...)` in DSL code. The command still returns JSON as a transport envelope, but the preview uses Clojure map/vector notation so access patterns are obvious: ```clojure (let [make-output (flow/step :code :make-output {...}) first-row (get-in make-output [:rows 0])] {:id (:id first-row)}) ``` For selective expansion: - `--result-path rows.0` or `--result-path '[:rows 0]'` previews one branch - `--preview-depth`, `--preview-items`, and `--preview-runes` raise bounded caps - `--result-file ./tmp/result.json` writes the full result locally without putting it in the CLI output - `--full` restores full `data.result` output Inline `--params ''` is fine for small input maps. Use `--params-file` for larger or reused inputs. ## Resource Refs Persisted outputs are passed as compact refs, not full payloads. Treat any `res://...` URI or map with `:uri` / `:resource-uri` as a handle that can be read later. In source, pass the step result map forward: ```clojure (let [resp (flow/step :http :fetch {:url "https://api.example.com/export" :method :get :persist {:type :blob}}) summary (flow/step :function :summarize {:input {:resp resp} :load [:resp] :code '(fn [{:keys [resp]}] {:bytes (:size resp) :resource (:uri resp)})})] summary) ``` Use `:load` only where the downstream step needs hydrated content. Many steps can pass refs through without loading the full body. Blob refs also carry storage-tier meaning: retained/default for durable or user-visible artifacts; ephemeral for temporary streamed HTTP blobs: ```clojure (flow/step :http :download-export {:url "https://api.example.com/export.zip" :method :get :response-as :bytes :persist {:type :blob :tier :ephemeral}}) ``` When a later function turns that ephemeral source into a final report, image, or table for users, persist the derived artifact intentionally on the retained default path. ## JSON And Sandbox Helpers Inside function steps: - use `json/parse` for JSON strings or bytes - use `json/write-str` when a downstream API needs a JSON string - use `breyta.sandbox/*` helpers for safe base64, hex, SHA-256, UUID, and instant parsing utilities - keep functions small and domain-focused Avoid hand-written parsers for values already represented as maps. ## Bounded Output Inline final output should be compact and user-facing. Large reports, tables, exports, generated media, and page sets should be persisted and returned as refs or viewer-friendly artifacts. For human-readable tables, return a persisted table resource. Do not return a giant inline JSON table as the final artifact. ## CLI/API Envelope Most CLI JSON output follows this shape: ```json { "ok": true, "data": {}, "meta": {}, "links": {} } ``` Use `data` for facts, `meta.nextCommands` for the next small command, `links` for URLs, and `error` for failed requests. Search/list commands are compact by default; use explicit `--full`, `--limit`, or read/show commands when deeper context is needed. ## References - [Flow Definition](/docs/reference-flow-definition) - [Step Function](/docs/reference-step-function) - [Persisted Results And Resources](/docs/guide-persisted-results-and-resources) - [Output Artifacts](REFERENCE_OUTPUT_ARTIFACTS.md) - [Table Resources](REFERENCE_TABLE_RESOURCES.md) --- Document: Templates URL: https://flows.breyta.ai/docs/reference-templates.md HTML: https://flows.breyta.ai/docs/reference-templates Last updated: 2026-02-13T15:21:05+01:00 # Templates Breyta templates reference for reusable prompts, HTTP payloads, SQL, and notification channel definitions. ## Quick Answer Define reusable content in top-level `:templates`, then reference by `:template` and supply runtime `:data`. ## Supported Template Types | Type | Primary use | Typical consumers | |---|---|---| | `:http-request` | Reusable HTTP request maps | `flow/step :http` | | `:llm-prompt` | Reusable system/user prompt content | `flow/step :llm` | | `:db-query` | Reusable SQL text and query shape | `flow/step :db` | | `:notification` | Reusable notification channel payloads/config | `flow/step :wait` (`:notify`), `flow/step :notify` | ## Definition Shape ```clojure {:templates [{:id :welcome :type :llm-prompt :system "You are concise." :prompt "Welcome {{user.name}}"} {:id :approval-email :type :notification :channels {:http {:connection :sendgrid :path "/v3/mail/send" :method :post :json {:subject "{{title}}" :content [{:type "text/plain" :value "Approve: {{approval-url}}\nReject: {{rejection-url}}"}]}}}}]} ``` ## Usage Patterns LLM prompt template: ```clojure (flow/step :llm :welcome-user {:connection :ai :template :welcome :data {:user {:name "Ada"}}}) ``` HTTP request template: ```clojure (flow/step :http :create-order {:connection :orders-api :template :create-order :data {:customer-id "cus_123" :amount 4200}}) ``` DB query template: ```clojure {:templates [{:id :recent-orders :type :db-query :sql "select * from orders where created_at >= {{from}}"}]} ``` Wait notification template: ```clojure (flow/step :wait :approval {:key "approval:123" :timeout "2h" :notify {:template :approval-email :data {:title "Order Approval"}}}) ``` ## Size And Reliability Notes | Topic | Guidance | |---|---| | Payload limits | Core flow payload cap is 150 KB; template/function content is budgeted separately (up to ~2.1 MB total package). | | When to template | Prefer templates over inline params for repeated payloads, large static bodies, and reviewable content. | | Notification reuse | Notification templates are primarily for wait/checkpoint `:notify`, and can be reused by `flow/step :notify` (`:http` channels). | | Large outputs | If step output may exceed inline thresholds, combine templates with `:persist`. | | Data contract | Keep variable names stable across template fields and step `:data` maps. | ## Related - [Notifications And Email](/docs/guide-notifications-and-email) - [Flow Definition](/docs/reference-flow-definition) - [Advanced Multi-Feature Flow](/docs/example-advanced-multi-feature) - [Limits And Recovery](/docs/reference-limits-and-recovery) --- Document: Connections First URL: https://flows.breyta.ai/docs/guide-connections-first.md HTML: https://flows.breyta.ai/docs/guide-connections-first Last updated: 2026-05-21T16:29:37+02:00 # Connections First Breyta guide for setting up reusable connections before wiring flow configuration. ## Goal Create, test, and reuse workspace connections up front so flow setup is faster and less repetitive. ## Quick Answer Use this sequence: ```bash breyta connections list breyta connections create --type http-api --name "Orders API" --base-url https://api.example.com breyta connections test breyta flows configure suggest breyta flows configure --set orders-api.conn= breyta flows configure check breyta flows run --wait ``` For human-supplied credentials, open the Breyta setup/connection UI and let the user enter secrets there. Do not ask for secrets in chat. Protected setup links should return through `/login?redirect=...`. LLM connection note: - Create LLM connections as `--type http-api` and choose an LLM backend. - Some UI create surfaces may still show a legacy option like `{:type :llm-provider :label "LLM Provider (Legacy)" :description "Legacy create mode normalized to HTTP API with an LLM backend" :icon :sparkles}`. - That legacy create-mode option still normalizes to a canonical `:http-api` connection. Authored flow requirements and docs should use `:http-api`. - For AWS Bedrock Claude, choose the Bedrock backend and AWS SigV4 auth. Use a Bedrock Runtime base URL such as `https://bedrock-runtime.us-east-1.amazonaws.com`, set `:region` to the same region, set `:service "bedrock"`, and store `access-key-id`, `secret-access-key`, plus optional `session-token` in the referenced secret. ## Why This Helps | Benefit | Outcome | |---|---| | Reuse-first setup | Fewer duplicate connections across flows. | | Early health checks | Fewer runtime surprises after wiring. | | Faster authoring loop | Flow wiring becomes a short mapping step. | ## Canonical Connect-First Workflow | Step | Command | Purpose | |---|---|---| | 1 | `breyta connections list [--type ]` | Reuse existing workspace connections before creating new ones. | | 2 | `breyta connections create ...` | Add a connection only when no reusable one exists. | | 3 | `breyta connections test ` | Test the specific connection you plan to bind or debug. Use `--all` only for a deliberate workspace-wide connection audit. | | 4 | `breyta connections items [--item-type ]` | Inspect cached non-secret provider items when a flow uses connection-backed dropdowns. | | 5 | `breyta connections usages --only-connected` | Inspect where each connection is already wired (`draft`/`live`/`installation`). | | 6 | `breyta secrets usages --only-connected` | Inspect where existing secret refs are already wired (`draft`/`live`/`installation`). | | 7 | `breyta flows configure suggest ` | Generate suggested `--set` values from existing healthy connections. | | 8 | `breyta flows configure --set .conn=` | Apply wiring for required slots and activation inputs. | | 9 | `breyta flows configure check ` | Confirm required config is complete. | | 10 | `breyta flows run --wait` | Verify runtime behavior. | | 11 | `breyta connections usages --connection-id ` | Confirm whether a connection can be safely deleted or still has bound usages. | ## Reuse-First Rules - Run `connections list` before every `connections create`. - Prefer one stable workspace connection per external system. - Use clear names (`Orders API`, `Billing DB`, `Support SMTP`) so reuse is obvious. - Test existing connections before wiring them into new flows. - When a flow uses connection item dropdowns, inspect `connections items` before assuming a repository, channel, project, or folder is available in setup/run UI. ## Missing Connection Handoff - Declare the `:requires` slot and provider/auth shape. - List/reuse/test plausible existing connections. - If none fits, stop and hand off the setup or connection edit `webUrl`. - Resume after the user saves the connection, then test, bind, and run proof. - For public/end-user flows, model user-owned accounts as `:provided-by :installer`. ## Custom OAuth Accounts Use a custom OAuth connection when a flow needs a user-authorized `http-api` account that is not covered by a built-in provider button. Custom OAuth connections store an OAuth client: your app's client ID and client secret. When a flow is shared, installers reuse the same client to authorize their own accounts. Each installer gets a separate user token and never sees your client secret. Who can do this: - Workspace creators/admins can create, edit, and reconnect custom OAuth accounts. Where to start in the UI: 1. Open `Connections`. 2. Choose `New connection`. 3. Choose `OAuth account`. 4. Choose `Use custom OAuth`. You can also start from flow setup when an OAuth-capable slot is unbound: 1. Open flow setup for the slot. 2. Choose `Use custom OAuth`. 3. Complete the connection form, then finish the browser OAuth redirect. The setup shape is: 1. Create a pending `OAuth account` connection. 2. Fill in the provider label, base URL, authorize URL, token URL, probe URL, scopes, and client credentials. 3. Complete the OAuth browser redirect. 4. Test the connection before wiring it into a flow. ### Before you start Gather these values from the provider's OAuth app settings and API docs: - client ID - client secret - authorization endpoint - token endpoint - a bearer-authenticated profile or `me` endpoint for health checks - required scopes - whether the provider requires PKCE - any provider-specific authorize parameters such as `audience`, `resource`, `access_type`, or `prompt` - non-standard authorize client ID names or scope separators, configured with `:oauth.authorize-params` and `:oauth.scope-separator` - non-standard token parameter names, configured with `:oauth.token-params` and `:oauth.refresh-token-params` Important client constraint: - The current custom OAuth form expects a confidential OAuth client with both a client ID and client secret. - Public-client-only setups are not supported by this form today, even when PKCE is enabled. ### Redirect URI to register with the provider Register this callback URL in the provider's OAuth app configuration: ```text https:///api/oauth/callback ``` Example: ```text https://flows.example.com/api/oauth/callback ``` Notes: - Replace `` with the base URL where your Breyta UI/API is served. - Prefer the generic callback URL above for new custom OAuth app registrations. - Existing custom OAuth apps that already use a workspace-scoped callback can keep working during migration with `https:////api/oauth/callback`. ### Field Guide | Field | What to enter | How to choose it | |---|---|---| | Provider label | Human-readable provider name such as `X`, `Salesforce sandbox`, or `Acme CRM` | Used only for display inside Breyta. | | Base URL | Base API URL used by the connection after OAuth succeeds | Usually the root API URL for later requests, such as `https://api.x.com/2`. | | Authorize URL | Provider authorization endpoint | The browser is redirected here so the user can approve access. | | Token URL | Provider token endpoint | Breyta exchanges the authorization code and refresh tokens here. | | Probe URL | A lightweight bearer-authenticated endpoint used for connection health checks | Prefer a `me`, `userinfo`, or profile endpoint that returns `200` for a valid token. | | Scopes | Space-delimited scopes | Start with the provider's minimum required scopes for the flow. | | Extra authorize params | Optional JSON object of extra query params added to the authorize request | Use only when the provider docs require it, for example `{"audience":"https://api.example.com","prompt":"consent"}`. | | Enable PKCE | Whether to send a PKCE challenge/verifier in the OAuth flow | Turn this on when the provider requires or recommends PKCE. | | Client ID / Client secret | OAuth client credentials from the provider app | Both are required when creating the connection. | ### Choosing a Probe URL The probe URL is used by `connections test` and other health checks to confirm that the token still works. Choose an endpoint that: - accepts the access token as a Bearer token - returns `200` for a healthy authenticated session - is lightweight and safe to call repeatedly - does not mutate provider state Good examples: - `https://api.x.com/2/users/me` - `https://openidconnect.googleapis.com/v1/userinfo` - `https://api.github.com/user` Avoid: - write endpoints - expensive search/list endpoints - endpoints that require request bodies or unusual headers beyond normal Bearer auth ### PKCE and Extra Authorize Params Use provider documentation to decide these values: - Enable PKCE when the provider requires PKCE or documents it as the recommended authorization-code setup. - Leave `Extra authorize params` blank unless the provider explicitly requires additional query parameters. Examples: ```json {"prompt":"consent"} ``` ```json {"audience":"https://api.example.com","access_type":"offline"} ``` Important behaviors: - Custom OAuth connections are workspace-scoped reusable connections, just like other connection types. - Changing OAuth endpoints, scopes, or client credentials marks the connection pending and requires reconnect. - Reconnects preserve the same connection id; flows bound to that connection do not need to be rewired. - Health checks validate the configured probe/token targets before sending tokens or refresh requests. - OAuth callback return paths are limited to relative workspace URLs. External return targets are rejected. ## Draft Vs Live Wiring | Target | Command | |---|---| | Draft (default) | `breyta flows configure --set .conn=` | | Live | `breyta flows configure --target live --version latest --set .conn=` | Use `breyta flows configure check ` for draft and `breyta flows configure check --target live --version latest` before the first live release of a new flow. ## Common Pitfalls - Creating a new connection when a matching healthy one already exists. - Wiring untested connections directly into flows. - Mixing draft/live config expectations and testing only one target. - Treating `connections test` as full runtime proof instead of a preflight check. - Assuming delete safety from flow code alone; usage guards are profile-binding based (`draft`/`live`/`installation`). ## Related - [Flow Configuration](/docs/guide-flow-configuration) - [CLI Workflow](/docs/guide-cli-workflow) - [CLI Commands](/docs/reference-cli-commands) - [Troubleshooting](/docs/guide-troubleshooting) --- Document: Functions URL: https://flows.breyta.ai/docs/reference-functions.md HTML: https://flows.breyta.ai/docs/reference-functions Last updated: 2026-05-15T14:59:18+02:00 # Functions Breyta functions reference for reusable Clojure transforms in `:function` steps. ## Quick Answer Define transform logic once in `:functions` and call by stable `:ref` from `flow/step :function` to keep orchestration small and deterministic. ## What Functions Are `functions` define reusable Clojure transforms referenced by `:function` steps. - isolate shaping and normalization logic - keep orchestration readable - avoid duplicating transform code across steps ## Definition Shape Declare functions at top level: ```clojure {:functions [{:id :summarize :language :clojure :code "(fn [input] {:count (count (:rows input))})"}]} ``` Call from flow: ```clojure (flow/step :function :summarize-rows {:ref :summarize :input {:rows rows}}) ``` ## Authoring Guidelines - keep function logic pure and deterministic - use functions for data transformation, not external side effects - use `:function` steps for non-trivial shaping/normalization, even if used once - keep inline `:flow` code focused on orchestration (ordering, branching, retries, waits) - keep ids stable; avoid renaming unless intentional - prefer multiline flow files over long one-line string literals ## Runtime Notes - function code is resolved by `:ref` from the flow definition - when running a function step in isolation via CLI, include `--flow ` so refs resolve - only allowlisted Java interop is available in the function runtime - sandboxed JSON helpers are exposed under `json`: - `json/parse` takes one string-or-bytes argument and keywordizes normal identifier-like JSON object keys by default - `json/write-str` serializes sandbox data back to a JSON string - keys that fail the conservative safety policy remain strings to avoid unbounded keyword interning - if `:flow` validation says it cannot resolve `Long/parseLong` or similar parser/helper calls, move that logic into a `:function` step or top-level `:functions` entry and use the supported `breyta.sandbox/*` or `json/*` helpers there Example: ```clojure {:functions [{:id :parse-webhook :language :clojure :code "(fn [input]\n (let [payload (json/parse (:body input))]\n {:event (:event payload)\n :id (get-in payload [:data :id])}))"}]} ``` ## Troubleshooting - unresolved `:ref`: confirm function id exists and matches exactly - parse/EDN failures: validate file structure and avoid accidental escaped newlines outside strings - JSON parsing/emitting in sandboxed functions: use `json/parse` and `json/write-str`; safe identifier-like keys become keywords, unusual keys stay strings, `json/parse` does not accept parser option flags, and lower-level parser namespaces are not part of the supported function-runtime surface - runtime errors: run step in isolation first, then run full flow after fix ## Related - [Templates](/docs/reference-templates) - [Flow Definition](/docs/reference-flow-definition) - [Troubleshooting](/docs/guide-troubleshooting) - [Flow Features And Config Toggles](/docs/guide-flow-features-and-config-toggles) --- Document: Flow Configuration URL: https://flows.breyta.ai/docs/guide-flow-configuration.md HTML: https://flows.breyta.ai/docs/guide-flow-configuration Last updated: 2026-05-21T16:29:37+02:00 # 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](/docs/guide-connections-first) - `breyta flows configure ...` for the default draft target - `breyta flows configure --target live ...` or `breyta flows installations configure ...` for live/installation targets - `breyta flows configure check ` (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 ` 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 `:llm` and `:agent` steps, 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 `:requires` contract 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`. ```clojure {: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: ```clojure {: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. ```clojure {: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: ```json { "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: ```clojure (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 :author` for author-owned platform credentials, such as an Apify or OpenAI account that should power every public install. - Use `:provided-by :installer` for 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: ```clojure {: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: ```clojure {:requires [{:kind :worker :label "Security review workers" :provided-by :author :job-types ["codex-review" "codex-fix"] :provider "breyta/jobs"}]} ``` Rules: - `:provided-by :author` means installers are not asked to supply the worker. - `:provided-by :installer` means 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. - `primaryDisplayConnectionSlot` is 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 --primary-display-connection-slot `. - Clear it with `breyta flows update --primary-display-connection-slot ''`. - Valid selectors come from declared `:requires` slots 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 `:connections` icon, then the first inferred `:requires` icon. - This metadata is workspace-scoped authoring state, not part of the pulled flow source file. Use `breyta flows show --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 --set activation.=`. - Configure installation inputs with `breyta flows installations configure --input '{"":""}'`. - At run time, these saved setup values are applied automatically for that target. Important lifecycle rule: - `breyta flows release ` and `breyta flows promote ` 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: ```clojure {: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//reports` until the installer explicitly saves another root in `bindings..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//storage//...`. They do not add any hidden `persist///` 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: ```text installations/inst-1/reports ``` That means a later step like: ```clojure :persist {:type :blob :slot :archive :path "exports/{{input.customer-id}}" :filename "summary.pdf"} ``` writes to: ```text workspaces//storage/installations/prof-1/reports/exports//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//reports` and `installations//reports`. Producer flow: ```clojure {:requires [{:slot :archive :type :blob-storage :label "Archive storage" :config {:prefix {:default "reports"}}}]} ``` Consumer flow: ```clojure {: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: ```clojure {: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 :slot` always points at a local blob-storage slot such as `:archive` - runtime resource pickers reuse the resolved `connection + root` behind 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: ```clojure {:requires [{:kind :form :fields [{:key :region :label "Region" :field-type :select :required true :options ["EU" "US"]}]}]} ``` Per-run input example: ```clojure {: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 `:file` resources and auto-selected for the current run - `:accept` supports exact MIME types like `"application/pdf"` and wildcard prefixes like `"text/*"` - add `:resource-types` only when you need something other than the default file picker, then combine it with `:accept` if needed - `:multiple true` lets the user append uploaded files to the current selection - `: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: ```clojure {:name :resources :label "Text resources" :type :resource :required true :multiple true :accept ["text/*" "application/json" "application/xml" "application/edn"]} ``` Inside the flow: ```clojure '(let [input (flow/input)] {:region (:region input) :question (:question input) :resources (:resources input)}) ``` Example input seen by the flow: ```clojure {: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 `:invocations` and 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 ` - `breyta secrets delete ` 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 ```clojure {: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: - `:label` for the recent-runs heading - `:title` as 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: ```clojure {: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-url` values 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 true` or `: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: ```clojure {: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 `:label` specific, for example `X account` instead of `OAuth` - include the expected API `:base-url` in the slot requirement when it is known - keep `:oauth.scopes.required` as small as possible - include `:oauth.requires-refresh-token true` when the flow expects long-lived reuse - use `:oauth.authorize-params` / `:oauth.scope-separator` for non-standard authorize parameters - use `:oauth.token-params` / `:oauth.refresh-token-params` for 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: ```text Configure an OAuth app at the provider and register: https:///api/oauth/callback Legacy custom OAuth installs can still use: https:////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 --set ...` | `breyta flows configure --target live --version --set ...` or `breyta flows installations configure --input ...` | | Check command | `breyta flows configure check ` | `breyta flows configure check --target live --version ` (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 --wait` | `breyta flows run --target live --wait` or `--installation-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 ` with no explicit installation target | [CLI Workflow](/docs/guide-cli-workflow) | | Installation target (`live` or explicit installation id) | Installation-specific values plus schedule/interface behavior | [Installations](/docs/guide-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 --target live --wait` - or `breyta flows run --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 --target live --version ` - `breyta flows configure --target live --version --set .conn=` - `breyta flows configure --target live --from-draft --version ` 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 ` 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](/docs/guide-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](/docs/guide-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 ## Related - [Flow Authoring](/docs/build-flow-authoring) - [Connections First](/docs/guide-connections-first) - [CLI Commands](/docs/reference-cli-commands) - [Troubleshooting](/docs/guide-troubleshooting) - [Webhooks And Secret Refs](/docs/guide-webhooks-and-secret-refs) --- Document: Orchestration Constructs URL: https://flows.breyta.ai/docs/reference-orchestration-constructs.md HTML: https://flows.breyta.ai/docs/reference-orchestration-constructs Last updated: 2026-04-14T14:57:17+02:00 # 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](/docs/reference-step-fanout) for bounded child-workflow fanout behavior. ## 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`: ```clojure ^{:label "Risk gate" :yes "Requires approval" :no "Auto-approve"} (if (:needs-approval summary) (flow/step :wait :approval {...}) {:action :approve}) ``` Loop metadata: ```clojure ^{: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: ```clojure ^{: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 - [Flow Definition](/docs/reference-flow-definition) - [Step Fanout](/docs/reference-step-fanout) - [Flow Features And Config Toggles](/docs/guide-flow-features-and-config-toggles) - [flow/poll](/docs/reference-flow-poll) - [Functions](/docs/reference-functions) - [Advanced Multi-Feature Flow](/docs/example-advanced-multi-feature) --- Document: Jobs Control Plane URL: https://flows.breyta.ai/docs/reference-flow-jobs.md HTML: https://flows.breyta.ai/docs/reference-flow-jobs Last updated: 2026-04-17T22:47:47+02:00 # Jobs Control Plane Canonical reference for submitting external work into Breyta's jobs control plane, awaiting normalized terminal results, and wiring worker outputs back into the flow. ## Quick Answer Use `flow/step :job` as the primary authoring surface. - `:op :submit` creates one queued external job - `:op :submit-batch` creates a queued batch - `:op :get` and `:op :get-batch` read the latest durable state - `:op :await` and `:op :await-batch` wait for terminal completion When a flow depends on external workers for installability or operator setup, declare that dependency in `:requires` with `{:kind :worker ...}`. The `:job` step describes runtime orchestration; the worker requirement describes the install/operator contract. ## Canonical Step Surface | Step op | Purpose | Return shape | |---|---|---| | `:submit` | Create one queued job. | Job map with fields such as `:job-id`, `:job-type`, `:status`, `:payload`. | | `:submit-batch` | Create a queued batch of jobs of the same type. | Batch map with `:batch-id`, aggregate counts, and `:jobs`. | | `:get` | Read the latest state of one job. | Job map. | | `:get-batch` | Read the latest state of one batch. | Batch map, optionally with `:jobs`. | | `:await` | Bounded wait for one job to become terminal. | Final job map. | | `:await-batch` | Bounded wait for one batch to become terminal. | Final batch map. | ## Single Job Example ```clojure '(let [{:keys [surface mode timeoutSeconds]} (flow/input) queued-job (flow/step :job :submit-agent-review {:op :submit :job-type "agent-review" :payload {:surface (or surface "flows-api") :mode (or mode "succeeded")} :metadata {:campaign "agent-review"}}) final-job (flow/step :job :await-agent-review {:op :await :job-id (:job-id queued-job) :interval "250ms" :timeout (str (long (or timeoutSeconds 60)) "s")})] final-job) ``` Matching worker command: ```bash export BREYTA_API_URL="https://flows.breyta.ai" export BREYTA_WORKSPACE="ws-acme" export BREYTA_API_KEY="" breyta jobs worker run --type agent-review --handler ./run-agent-review.sh ``` Recommended operator setup for that worker identity: ```bash breyta service-accounts create \ --name agent-review-worker \ --scope jobs.worker \ --job-type agent-review breyta service-accounts keys create --name agent-review-key ``` `--scope` accepts repeated flags or comma-separated values. `--capability` remains accepted as a compatibility alias. `jobs.worker` is the minimum worker scope. If the same machine should also inspect or mutate broader Breyta state, grant additional service-account scopes such as `flows.read`, `flows.manage`, `flows.run`, `resources.read`, `resources.write`, or `workspace.full` according to the automation boundary you want. That worker injects the local job directory and the API context the handler-side commands use: - `BREYTA_JOB_DIR` - `BREYTA_JOB_CONTEXT_FILE` - `BREYTA_JOB_FILE` - `BREYTA_JOB_PAYLOAD_FILE` - `BREYTA_JOB_RESULT_FILE` - `BREYTA_JOB_ID` - `BREYTA_JOB_TYPE` - `BREYTA_JOB_WORKSPACE_ID` - `BREYTA_API_URL` - `BREYTA_WORKSPACE` - `BREYTA_API_KEY` or `BREYTA_TOKEN` Use `breyta jobs show ` for the durable job state the flow can await. Use `breyta jobs worker state` inside the handler, or with `--job-dir `, to inspect the local `job.json`, `payload.json`, and `result.json` state while the worker is building the result. That handler reads the injected `payload.json`, emits progress through `breyta jobs worker progress`, optionally attaches resource-backed artifacts or structured resources, and marks terminal state through `breyta jobs worker finish` or `breyta jobs worker fail`. Those helper commands reuse the worker context plus the worker API/workspace/auth env injected by `breyta jobs worker run`. `BREYTA_JOB_CONTEXT_FILE` is an opaque worker context for those helper commands; handlers typically read `payload.json` and write result state instead of parsing that file directly. Minimal handler implementation: ```bash #!/usr/bin/env bash set -euo pipefail surface="$(python3 - "$BREYTA_JOB_PAYLOAD_FILE" <<'PY' import json import sys with open(sys.argv[1], "r", encoding="utf-8") as handle: payload = json.load(handle) print(payload.get("surface", "flows-api")) PY )" report_path="$BREYTA_JOB_DIR/review-report.md" cat >"$report_path" <` Example: ```clojure (let [base "https:///" callback-url (str base "/events/" wait-key)] ...) ``` Important: - Your VM/VPS must be able to reach the callback URL. - For local testing, use a tunnel (ngrok/cloudflared) or run flows-api on a reachable host. ### 3) Kick off the agent over SSH Pass callback values to the remote process as environment variables: ```clojure (flow/step :ssh :kickoff {:title "Kick off remote agent" :connection :vps :command "nohup ./run-agent.sh >/tmp/agent.log 2>&1 & echo started" :env {"BREYTA_WAIT_KEY" wait-key "BREYTA_CALLBACK_URL" callback-url} :timeout 60}) ``` ### 4) Wait for the callback ```clojure (flow/step :wait :await-callback {:title "Wait for agent callback" :key wait-key :webhook {:auth {:type :none}} ; dev-friendly; use HMAC/signature in production :timeout 3600 :on-timeout :fail}) ``` ### 5) Remote agent posts final output The remote agent should POST JSON to `BREYTA_CALLBACK_URL`. Example: ```bash curl -X POST "$BREYTA_CALLBACK_URL" \ -H 'content-type: application/json' \ -d '{"status":"ok","result":{"answer":42}}' ``` The JSON payload becomes the `:wait` step result. ## When To Use Sync SSH Instead Use a single `:ssh` step (sync) when: - the remote work is short-lived (seconds/minutes) - you want the flow to wait naturally on the command exit status See: [Step SSH](/docs/reference-step-ssh). ## Should We Add An “Agent” Step? Maybe later — but it’s usually best to start with the primitive (`:ssh`) and document the **agent pattern**. What a dedicated `:agent` step could do (as a wrapper around `:ssh` + `:wait`): - Standardize correlation (`wait-key`) and callback URL generation. - Provide a canonical env contract (`BREYTA_WAIT_KEY`, `BREYTA_CALLBACK_URL`, optional auth headers). - Offer a higher-level “kickoff + await result” interface with sensible defaults (timeouts, retries, max-output). - Normalize the callback payload into a stable result shape (e.g. `{status, result, logsUrl, artifacts}`). - Optionally stream progress via UI notifications (and/or accept progress callbacks). Why start with `:ssh` first: - It keeps the platform composable: `:ssh` works for many non-agent tasks. - It avoids baking an “agent runtime” contract too early. - You can still market “Remote Agent Kickoff” as a documented pattern and ship a `:agent` step once you see repeated configs in user flows. ## VPS Provisioning (Options) ### Bring your own VPS (BYO) You provide: - host/port/username - SSH auth (private key or password via secret refs) - host key verification (`known_hosts`) Pros: - Works with any provider - Maximum flexibility Cons: - More setup work (keys, host keys, firewalling) ### Breyta-managed VPS per workspace (future direction) In this model, Breyta would provision and manage a small VM per workspace and provide a standard “agent runtime”. Potential benefits: - Faster onboarding (“it just works”) - Predictable runtime environment and paths - Easier support and upgrades Operational considerations: - lifecycle + billing + quotas - security posture, patching, auditing - abuse prevention and network egress controls - escape hatches (BYO VPS remains supported) - secrets management UX (keys, host keys, callback auth) and rotation story ## Related - [Set Up A VPS Or VM For Breyta SSH](/docs/guide-set-up-vps-vm-for-ssh) - [Step SSH](/docs/reference-step-ssh) - [Step Wait](/docs/reference-step-wait) - [Waits, Signals, And Timeouts](/docs/guide-waits-signals-and-timeouts) - [Webhooks And Secret Refs](/docs/guide-webhooks-and-secret-refs) --- Document: SSH Testing URL: https://flows.breyta.ai/docs/guide-ssh-testing.md HTML: https://flows.breyta.ai/docs/guide-ssh-testing Last updated: 2026-03-13T15:40:26+01:00 # SSH Testing Test the `:ssh` step locally (mock mode), and against a real VM/VPS (real SSH). If you still need to set up the VPS/VM, SSH user, key material, and Breyta connection from scratch, start with [Set Up A VPS Or VM For Breyta SSH](/docs/guide-set-up-vps-vm-for-ssh). ## Local (Mock) — Sync SSH Use this only for short command validation. For long-running agent workloads, test the async pattern in this guide (`ssh-async-callback`) instead of a single sync `:ssh` step. ### 1) Start a local flows-api with demo flows From `breyta/`: ```bash ./scripts/start-flows-api.sh --profile=memory --seed --demos ``` This starts: - API: `http://localhost:8090` - nREPL: `localhost:7889` - Demo flows are seeded into workspace `ws-acme` The `breyta` commands below assume your local CLI defaults are already pointing at that local dev API and workspace, so the commands omit explicit `--api`, `--workspace`, and `--token` flags. ### 2) Create a mock SSH connection ```bash breyta --dev \ connections create \ --type ssh --backend ssh --name "Mock VPS (SSH)" \ --config '{"host":"example.com","port":22,"username":"ubuntu","mode":"mock"}' ``` Copy the returned `connection-id`. ### 3) Configure the demo flow and run it Bind the SSH connection to the flow’s `:vps` slot: ```bash breyta --dev \ flows configure ssh-exec --set vps.conn= ``` Run and wait: ```bash breyta --dev \ flows run ssh-exec --wait \ --input '{"command":"echo hi"}' ``` Expected: a completed run whose step result includes `stdout` starting with `MOCK SSH`. ## Local (Mock) — Async Agent Callback Pattern This uses the demo flow `ssh-async-callback`, which: - kicks off an “agent/job” via `:ssh` (quick) - pauses via `:wait` - resumes when something POSTs JSON to the callback URL ### 1) Configure the demo flow ```bash breyta --dev \ flows configure ssh-async-callback --set vps.conn= ``` ### 2) Start the run (don’t wait yet) ```bash breyta --dev \ flows run ssh-async-callback \ --input '{"command":"echo started"}' ``` Copy the returned `workflowId` (looks like `flow-ssh-async-callback-ws-acme-r1`). ### 3) Compute the wait key and POST the callback In this demo flow, the wait key is deterministic: ```text wait-key = "agent-" + workflowId ``` Example callback: ```bash curl -X POST \ "http://localhost:8090/ws-acme/events/agent-" \ -H 'content-type: application/json' \ -d '{"status":"ok","result":{"answer":42}}' ``` ### 4) Confirm the run completes ```bash breyta --dev \ runs show ``` Expected: status `completed` and `result.callback.result.answer == 42`. ## Real SSH — Connect To A VM/VPS For production-like long jobs, validate with the async kickoff + callback flow pattern. Do not rely on a single sync `:ssh` step to prove long-duration reliability. To connect to a real host, you need: - `host` (DNS or IP) - `port` (usually `22`) - `username` (Linux user) - auth: **private key** (recommended) or **password** - host key verification: `known_hosts` (recommended) ### 1) Create `known_hosts` for the target host ```bash mkdir -p ./tmp ssh-keyscan -H -p 22 > ./tmp/ssh-known-hosts ``` ### 2) Store secrets in the workspace secret-store (via flow configuration) The seeded demo flows declare optional secret slots that store secrets under these refs: - `ssh-private-key` - `ssh-known-hosts` - `ssh-key-passphrase` (optional) Bind secret values using `--set ...=@file` for multiline content. Use `@/absolute/path` or `@./relative/path`; do not use `@~/.ssh/...`, because shells do not expand `~` reliably in that form: ```bash breyta --dev \ flows configure ssh-exec \ --set ssh-private-key.secret=@$HOME/.ssh/id_ed25519 \ --set ssh-known-hosts.secret=@./tmp/ssh-known-hosts ``` Use the same key file here that already works in a direct manual SSH check. Do not assume your local SSH agent and Breyta are using the same key automatically. If your key is encrypted: ```bash breyta --dev \ flows configure ssh-exec \ --set ssh-key-passphrase.secret=@./tmp/ssh-passphrase.txt ``` ### 3) Create a real SSH connection that references those secret refs ```bash breyta --dev \ connections create \ --type ssh --backend ssh --name "My VPS (SSH)" \ --config '{ "host":"", "port":22, "username":"", "auth":{"type":"private-key","secret-ref":"ssh-private-key","passphrase-secret-ref":"ssh-key-passphrase"}, "known-hosts":{"secret-ref":"ssh-known-hosts"} }' ``` ### 4) Bind the real connection and run ```bash breyta --dev \ flows configure ssh-exec --set vps.conn= ``` ```bash breyta --dev \ flows run ssh-exec --wait \ --input '{"command":"uname -a"}' ``` ## Related - [Set Up A VPS Or VM For Breyta SSH](/docs/guide-set-up-vps-vm-for-ssh) - [Step SSH](/docs/reference-step-ssh) - [Remote Agents (SSH)](/docs/guide-remote-agents-ssh) - [Secrets](/docs/guide-secrets) --- Document: Waits, Signals, And Timeouts URL: https://flows.breyta.ai/docs/guide-waits-signals-and-timeouts.md HTML: https://flows.breyta.ai/docs/guide-waits-signals-and-timeouts Last updated: 2026-02-13T15:21:05+01:00 # Waits, Signals, And Timeouts Breyta `:wait` step guide for human approvals, webhook callbacks, and timeout-safe orchestration. ## Goal Use `:wait` as the canonical human-in-the-loop and external callback primitive. ## Quick Answer Use `flow/step :wait` with a stable `:key`, explicit `:timeout`, and a clear `:on-timeout` policy. ## Canonical Wait Pattern ```clojure {:flow '(let [input (flow/input) approval (flow/step :wait :approval {:key (str "order-approval:" (:order-id input)) :timeout "2h" :on-timeout :continue :default-value {:action :reject :reason :timeout} :notify {:channels {:http {:connection :sendgrid :path "/v3/mail/send" :method :post :json {:personalizations [{:to [{:email "approver@example.com"}]}] :from {:email "approvals@example.com"} :subject "Approve order {{order-id}}" :content [{:type "text/plain" :value "Approve: {{approval-url}}\nReject: {{rejection-url}}"}]}}}}})] {:action (:action approval) :approval approval})} ``` ## Wait Modes | Mode | Required shape | Typical use | |---|---|---| | Approval notification | `:notify {:channels {:http ...}}` | Human approval/review workflows. | | Webhook callback | `:webhook {:path ... :auth ...}` | External system callback completion. | | Internal signal-only | `:key` + `:timeout` | Internal orchestration handoff waits. | ## Timeout Semantics | Setting | Behavior | |---|---| | `:timeout` | Accepts seconds or duration strings like `"10m"` / `"2h"`. | | Default timeout | 24h. | | Max wait timeout | 30 days. | | `:on-timeout :fail` | Run fails on timeout. | | `:on-timeout :continue` | Wait returns `:default-value` and flow continues. | ## Design Rules | Rule | Why | |---|---| | Always set explicit `:timeout`. | Avoids implicit/default timeout surprises. | | Use deterministic, traceable `:key` values. | Ensures signal path is debuggable and reproducible. | | Include business identifiers in notifications. | Gives operators enough context to act. | | Branch approve/reject/timeout outcomes explicitly. | Keeps business behavior predictable. | ## Troubleshooting Signals | Symptom | Check | |---|---| | Wait never resolves | Verify wait key/path and confirm completion signal was sent. | | Timeout fires unexpectedly | Validate timeout unit (`"2h"` vs `120`) and queue delays. | | Payload missing fields | Enforce required fields with `:validation {:required [...]}`. | ## Frequently Asked Questions ### Should I use `:timeout-seconds` or `:kind :signal`? No. The canonical wait config uses `:timeout` and `:key`. Legacy fields should be avoided in new flows. ### How do I avoid stuck approval runs? Bound every wait with explicit timeout and a business-safe timeout branch (`:fail` or `:continue` + `:default-value`). ## Related - [Wait For Approval](/docs/example-wait-for-approval) - [Advanced Multi-Feature Flow](/docs/example-advanced-multi-feature) - [Notifications And Email](/docs/guide-notifications-and-email) - [Limits And Recovery](/docs/reference-limits-and-recovery) --- Document: flow/poll URL: https://flows.breyta.ai/docs/reference-flow-poll.md HTML: https://flows.breyta.ai/docs/reference-flow-poll Last updated: 2026-05-15T14:59:18+02:00 # flow/poll Canonical reference for `flow/poll`: bounded polling loops for external async work using deterministic orchestration helpers. ## Quick Answer Use `flow/poll` when you need to repeatedly check external job status. Always bound it with `:timeout` or `:max-attempts`, and define success with `:return-on`. ## When To Use | Scenario | Why `flow/poll` fits | |---|---| | You start a remote job and must check status until completion. | Encodes bounded retry loop with clear completion criteria. | | Readiness is determined by response fields, not callback push. | Keeps polling logic in one deterministic config map. | | You need deterministic retry/backoff without hand-written loop boilerplate. | Uses first-class bounds/backoff and standardized semantics. | Prefer `:wait` when an external actor pushes a callback/signal (human approvals, webhook completions). ## Config Shape `flow/poll` requires a literal config map. ```clojure (flow/poll {:interval "10s" :timeout "20m" ;; or :max-attempts :backoff {:type :exponential :factor 2 :max "2m"} :abort-on {:status #{400 401 403} :error? true} :return-on (fn [result] (= :completed (:status result))) :id :order-status-poll-sleep} (flow/step :http :fetch-job-status {:connection :jobs-api :method :get :path (str "/jobs/" job-id)})) ``` Important fields: | Field | Required | Meaning | |---|---|---| | `:interval` | Yes | Poll interval (`"10s"` style duration or milliseconds). | | `:timeout` | Conditionally | Duration/ms timeout cap. | | `:max-attempts` | Conditionally | Attempt cap. | | `:timeout` or `:max-attempts` | At least one | At least one bound is required. | | `:return-on` | Yes | Predicate function/symbol for success completion. | | `:abort-on` | No | Stop condition for statuses/errors. | | `:backoff` | No | Interval progression (`:constant`, `:linear`, `:exponential`). | | `:id` | No | Stable generated sleep step id label. | ## High-Level Implementation Model `flow/poll` is expanded before execution into ordinary orchestration primitives: | Runtime behavior | Primitive | |---|---| | Capture poll start time | `flow/now-ms` | | Track attempts | `loop` / `recur` | | Execute poll body | normal `flow/step` body | | Evaluate completion condition | `:return-on` predicate | | Apply stop rules | abort conditions + bounds | | Sleep between attempts | `flow/step :sleep` | | Compute next interval | `flow/backoff` | | Enforce timeout | `flow/elapsed?` | Practical implication: you get a reusable polling primitive built from the same deterministic constructs you use directly. ## Advanced Pattern: Poll + Persist + Compact Output ```clojure '(let [input (flow/input) started (flow/step :http :start-export {:connection :reports-api :method :post :path "/exports" :json {:account-id (:account-id input)}}) job-id (:job-id started) final-status ^{:label "Wait for export completion"} (flow/poll {:interval "5s" :timeout "15m" :backoff {:type :exponential :factor 1.5 :max "60s"} :abort-on {:status #{400 404 500} :error? true} :return-on (fn [r] (= :completed (:status r))) :id :export-status-sleep} (flow/step :http :get-export-status {:connection :reports-api :method :get :path (str "/exports/" job-id)})) artifact (flow/step :function :persist-export-status {:input final-status :code '(fn [input] input) :persist {:type :blob}})] {:job-id job-id :status (:status final-status) :status-ref (:uri artifact)}) ``` This keeps orchestration clear and avoids large inline payloads while polling. ## Using Implicit Flow Context In Custom Patterns `flow/poll` body runs inside your flow orchestration context, so it can safely use: | Context source | Typical use | |---|---| | Previously bound `let` values | Reuse ids, cursors, and guard flags. | | Connection/binding-driven configs | Keep auth/routing in target bindings. | | Deterministic helper outputs | Reuse `flow/now-ms`, persisted refs, and stable keys. | Patterns that work well: | Pattern | Shape | |---|---| | Staged polling | Fast initial poll, then slower secondary poll branch. | | Status-class routing | Poll to terminal status, then branch with labeled `if`/`cond`. | | Child-flow health polling | `flow/call-flow` + status poll in parent orchestrator. | ## Validation Rules You Should Expect | Rule | Validation expectation | |---|---| | Config shape | Must be a literal map. | | `:return-on` | Required and must be function/symbol. | | `:timeout` | Must parse as duration/ms when provided. | | Linear backoff | `:backoff {:type :linear}` requires `:step`. | | Bounded execution | At least one bound is required (`:timeout` or `:max-attempts`). | See platform caps in [Limits And Recovery](/docs/reference-limits-and-recovery). ## Related - [Orchestration Constructs](/docs/reference-orchestration-constructs) - [Limits And Recovery](/docs/reference-limits-and-recovery) - [Patterns (Do/Do Not)](/docs/guide-patterns-do-dont) - [Waits, Signals, And Timeouts](/docs/guide-waits-signals-and-timeouts) --- Document: Run Concurrency URL: https://flows.breyta.ai/docs/reference-run-concurrency.md HTML: https://flows.breyta.ai/docs/reference-run-concurrency Last updated: 2026-05-10T21:06:10+02:00 # Run Concurrency Breyta flow run concurrency reference for controlling overlap, per-key execution, and version handoff behavior. ## Quick Answer Flow run concurrency is controlled by `:concurrency` on the flow definition: - choose model: `:singleton` or `:keyed` - choose version policy: `:supersede`, `:drain`, or `:coexist` This is run-level behavior (between workflow executions), not per-step execution shaping. ## Config Shape | Field | Required | Values | Notes | |---|---|---|---| | `:type` | Yes | `:singleton`, `:keyed` | Defines run isolation model. | | `:on-new-version` | Yes | `:supersede`, `:drain`, `:coexist` | Defines behavior when a new released version starts receiving runs. | | `:key-field` | For `:keyed` | keyword or path vector | Extracts run key from input (for example `:order-id` or `[:event :id]`). | ## Model Behavior | Model | What is unique | Typical use | |---|---|---| | `:singleton` | One active run slot per flow slug/workspace | Scheduled rollups, singleton workers, periodic sync jobs | | `:keyed` | One active run slot per key value | Entity/event scoped flows (order, user, ticket, webhook event) | ## Version Handoff Policy | `:on-new-version` | Behavior | Use when | |---|---|---| | `:supersede` | Running older-version executions are terminated and new version runs immediately. | Fast cutover is more important than finishing in-flight work. | | `:drain` | Running older-version executions finish first; new version runs wait. | In-flight work must complete before cutover. | | `:coexist` | Older and newer versions can run at the same time. | Parallel transition is acceptable and version overlap is intentional. | ## Canonical Examples ```clojure ;; One active run at a time; cut over immediately on release. {:concurrency {:type :singleton :on-new-version :supersede}} ;; One active run per order id; drain old version before key-wise cutover. {:concurrency {:type :keyed :key-field :order-id :on-new-version :drain}} ``` ## Practical Guidance 1. Start with `:singleton` unless you explicitly need per-entity parallelism. 2. Use `:keyed` only when every invocation/interface/schedule path reliably supplies the key field. 3. Pick `:drain` when side effects must finish before version change. 4. Pick `:supersede` for incident fixes/hot releases where immediate behavior change matters. 5. Use `:coexist` deliberately and monitor version mix in runs after release. ## Common Mistakes - Confusing flow run concurrency with per-step retries/timeouts (different scope). - Choosing `:keyed` without ensuring input always contains the key field. - Changing version policy without validating overlap behavior for each invocation/interface/schedule path. ## Related - [Flow Definition](/docs/reference-flow-definition) - [Flow Features And Config Toggles](/docs/guide-flow-features-and-config-toggles) - [Flow Basics](/docs/build-flow-basics) - [Scheduled Rollup](/docs/example-scheduled-rollup) - [Advanced Multi-Feature Flow](/docs/example-advanced-multi-feature) --- Document: Troubleshooting URL: https://flows.breyta.ai/docs/guide-troubleshooting.md HTML: https://flows.breyta.ai/docs/guide-troubleshooting Last updated: 2026-02-17T09:28:57+01:00 # Troubleshooting Breyta flow troubleshooting guide for push, bindings, wait, and runtime step failures. ## Quick Answer Triage in this order: validate flow syntax, confirm flow exists in target source, isolate failing step, verify bindings/secrets, then rerun full workflow. ## Triage Matrix | Failure mode | Symptoms | Checks | Next action | |---|---|---|---| | Push fails with EDN parse error | Push fails with parse errors; odd forms in map/bindings. | Run `breyta flows paren-check `.
Verify file has real newlines (not escaped `\n` outside strings).
Re-apply minimal edit and push again. | Fix delimiter/EDN structure first, then retry push. | | Flow not found during configuration | `flows configure` or run commands fail with flow-not-found. | Run `breyta flows show `.
If missing, push/create flow first.
Continue only after show succeeds. | Re-establish workspace flow state in API mode before configuring/running. | | Step fails in full run | Workflow fails but root cause is unclear. | Run failed step in isolation (`breyta steps run ...`).
For function refs include `--flow `.
Add/verify retries only for transient failures. | Rerun full workflow only after isolated step succeeds. | | Wait never resolves | Runs remain in wait longer than expected. | Confirm explicit `:timeout`.
Ensure payload has identifiers needed for signaling.
Branch timeout path explicitly. | Make wait deterministic (`:key`, `:timeout`, timeout branch). | | Configuration/secret errors at runtime | Unauthorized responses; missing secret or connection errors. | Ensure all required slots are configured (`breyta flows configure --set ...`).
Verify installation target settings when using `--target live` or `--installation-id`. | Reapply configuration, then rerun with `breyta flows run --wait`. | ## Connection Reuse Check (Avoid Duplicates) | Step | Command / check | |---|---| | 1 | Run `breyta connections list`. | | 2 | Confirm `:requires` slots point to existing workspace connections. | | 3 | Create new connection only when no existing one matches provider/environment/auth. | ## Frequently Asked Questions ### Why do configure commands fail with "flow not found"? Because the flow was not pushed successfully; verify with `breyta flows show ` first. ### When should I run full workflow again? Only after the failing step passes in isolation and required bindings are confirmed. ## Related - [Flow Configuration](/docs/guide-flow-configuration) - [Waits, Signals, And Timeouts](/docs/guide-waits-signals-and-timeouts) - [Functions](/docs/reference-functions) --- Document: Persisted Results And Resource Refs URL: https://flows.breyta.ai/docs/guide-persisted-results-and-resources.md HTML: https://flows.breyta.ai/docs/guide-persisted-results-and-resources Last updated: 2026-05-20T03:00:25+02:00 # Persisted Results And Resource Refs Breyta persistence guide for storing large step outputs as `res://` references and retrieving artifacts safely. ## Goal Store large step outputs as resource refs and pass compact references across steps. ## Quick Answer Use `:persist` when output size is uncertain, pass refs downstream, and inspect content with `breyta resources read `. For rows, `:persist {:type :table ...}` creates a queryable table resource for later `:table` steps and `breyta resources table ...`. For blobs, choose the tier deliberately: retained/default for durable or user-visible artifacts; `:tier :ephemeral` for temporary streamed HTTP responses. Persistence is storage, not presentation. A `res://...` ref is a compact handle for downstream steps and debugging. When a person should see the result, render the resource through a final output viewer: usually a Markdown report with `breyta-resource` fences, or a deliberate `:table`, `:image`, `:video`, `:download`, or `:raw` viewer. Persisted JSON resources can also render through `:view :json` inside Markdown. See [Output Artifacts](/docs/guide-output-artifacts). ## Why Use `:persist` Use persistence when a step can produce large or unbounded output: - avoid bloating inline workflow state - keep downstream step params shareable and small - surface retrievable artifacts via `breyta resources ...` - avoid frequent rework from crossing the 512 KB inline threshold during iteration - keep row-oriented operational data in a queryable table resource instead of pushing whole rowsets through workflow history The important hard numbers for authors are: - inline step results are intended to stay under `512 KB` - unpersisted step results hard-fail around `1 MB` - database result payloads are capped at `1 MB` - retained blob persists can write up to `50 MB` - ephemeral streamed blob persists can write up to `4 GB` - HTTP body loads from refs are capped at `10 MB` retained or `20 MB` ephemeral So for data-heavy outputs that can exceed inline limits, return `:persist` refs instead of passing the whole value through workflow state. ## Storage Tier Decision `:persist` has two choices: `:type` chooses `:blob`, `:table`, or `:kv`; `:tier` chooses the blob storage tier where supported. | Tier | How to request it | Use when | Typical cap | | --- | --- | --- | --- | | retained | omit `:tier` or use `:tier :retained` | durable, reusable, searchable, user-visible, or needed after the run | `50 MB` write cap | | ephemeral | `:tier :ephemeral` | temporary streamed HTTP downloads, exports, generated media, or API response bodies | `4 GB` streaming write cap | Current support boundary: `:tier :ephemeral` is for streaming HTTP blob persists. Function, table, and KV persists use the retained/default path today. Retain the final curated artifact or table; keep intermediate HTTP blobs ephemeral. ## Document Fetch, Preserve, Extract, Display For PDFs and other document files, separate four jobs: - fetch: use `:http` with `:response-as :bytes` - preserve: add `:persist {:type :blob ...}` so the file has a `res://` ref - extract: use an external document extraction API or an LLM/tool that explicitly supports that file type - display: return a Markdown report with a download/resource embed Breyta does not currently expose a built-in PDF text extraction primitive. Do not assume that persisting a PDF makes its raw text available to functions or search. Persisted PDFs are discoverable by metadata/path context; add `:persist {:search-index {:text ...}}` when you already have trusted extracted text. If an HTTP step returns binary bytes, do not pass those bytes to table rows as text. Persist the blob, pass the resource ref, then call an explicit extraction service before writing extracted fields to a table. ## Default Posture For non-trivial flows, default to `:persist` for steps that can return variable or growing payloads (`:db`, `:http`, `:llm`), then pass refs downstream. When the result is a collection of rows that should stay editable or queryable later, prefer `:persist {:type :table ...}` over keeping the full rowset inline. For derived tables, prefer `flow/step :table` with `{:op :materialize-join ...}` over pulling rows into `:function` and hand-writing joins. ## Minimal Pattern Blob persist: ```clojure {:flow '(let [download (flow/step :http :download-orders {:url "https://api.example.com/orders.csv" :response-as :bytes :persist {:type :blob :tier :ephemeral :filename "orders.csv" :content-type "text/csv"}})] {:download-uri (:uri download)})} ``` Persisted blob results include the resource ref fields used by resource APIs, viewers, tables, and downstream loaders: ```clojure {:uri "res://..." :resource-uri "res://..." :blob-ref {...}} ``` Table persist: ```clojure {:flow '(let [orders (flow/step :http :fetch-orders {:url "https://api.example.com/orders" :accept :json :persist {:type :table :table "orders" :rows-path [:body :items] :write-mode :upsert :key-fields [:id]}})] {:orders-table orders :orders-table-uri (:uri orders)})} ``` Table persists return a resource ref with table/write metadata: ```clojure {:type :resource-ref :uri "res://v1/ws/ws-123/result/table/tbl_..." :content-type "application/vnd.breyta.table+json" :preview {:table-name "orders" :write-mode :upsert :rows-written 100} :write {:mode :upsert :rows-written 100}} ``` `upsert` is incremental: it updates matching key rows and inserts new key rows, but it does not remove rows omitted from a later write. For "latest snapshot" tables, include a run/batch key in `:key-fields` or `:partitioning`, then query the current batch. There is no scoped replace/delete-by-group mode yet. ## Human-Readable Table Output Recipe Use this pattern when the final output should open as a real Breyta table artifact, not just show a text report with a table inside it. 1. Build row maps with stable storage keys. 2. Add human-facing column metadata with `:columns`. 3. Persist the rows with `:persist {:type :table ...}`. 4. Return the persisted table step result as the `:breyta.viewer/value` for a `:table` viewer. ```clojure '(let [run-id (str "run-" (flow/now-ms)) comparison-table (flow/step :function :build-comparison-table {:input {:rows comparison-rows :run-id run-id} :code '(fn [{:keys [rows run-id]}] {:rows (mapv (fn [row] {:run_id run-id :paragraph (:paragraph row) :original (:original row) :cleaned (:cleaned row) :changed (:changed row)}) rows)}) :persist {:type :table :table (str "transcript-comparison-" run-id) :rows-path [:rows] :write-mode :upsert :key-fields [:run_id :paragraph] :indexes [{:field :run_id} {:field :changed}] :columns [{:column :paragraph :display-name "Paragraph"} {:column :original :display-name "Original"} {:column :cleaned :display-name "Cleaned"} {:column :changed :display-name "Changed"}]}})] {:breyta.viewer/kind :table :breyta.viewer/options {:title "Original vs cleaned"} :breyta.viewer/value comparison-table}) ``` Inline maps like `{:rows [...] :columns [...]}` are not table artifacts. A real table artifact has a table content type and a `res://.../result/table/...` URI. When the table belongs inside a narrative report, keep the final output as a Markdown viewer and embed the persisted table with a `breyta-resource` fence. That lets the surrounding text, filtered table snapshot, aggregate chart, and download affordance render in document order without exposing the `res://` URI to end users. Use [Output Artifacts](/docs/guide-output-artifacts) for the full Markdown resource embed syntax. Verification loop: ```bash breyta runs show --pretty breyta resources read --limit 25 --offset 0 ``` Check that the final output table item contains `:type :resource-ref`, `:content-type "application/vnd.breyta.table+json"`, and non-zero `:preview :rows-written` or `:preview :row-count`. `materialize-join` remains incremental in v1: - destination writes use `:append` or `:upsert` - there is no snapshot/replace mode yet - joins read the current materialized row state of source tables, so `:recompute` first when derived source values must be refreshed The same rule applies to ordinary table persists: a smaller rerun does not delete rows from an earlier larger run unless the flow models each run as its own batch/partition and reads the latest batch. ## Loading Persisted HTTP Responses In Function Steps For large persisted HTTP responses, pass the whole step result into the downstream function input and mark that field in `:load`. Use `:tier :ephemeral` for temporary intermediates; omit `:tier` when the HTTP blob itself is durable. ```clojure '(let [resp (flow/step :http :generate-image {:connection :image-api :method :post :path "/images/generations" :persist {:type :blob :tier :ephemeral}}) img (flow/step :function :decode-image {:input {:resp resp} :load [:resp] :persist {:type :blob :filename "image.jpeg" :content-type "image/jpeg"} :code '(fn [{:keys [resp]}] (-> resp :body :data first :b64_json breyta.sandbox/base64-decode-bytes))})] {:image img}) ``` Use this pattern when the HTTP response body is too large to survive inline transfer to the next step. ## Blob Path Templates Use `:path` to express the relative storage subpath and keep `:filename` as the leaf name: ```clojure (flow/step :function :persist-report {:input {:tenant-id tenant-id :report-id report-id :rows rows} :code '(fn [{:keys [rows]}] rows) :persist {:type :blob :path "exports/{{input.tenant-id}}" :filename "report-{{input.report-id}}.json"}}) ``` For plain `:persist` writes without `:slot`, runtime stores the artifact under its managed prefix: ```text workspaces//persist////exports//report-.json ``` Notes: - `:path` is relative only; do not include a leading `/` or `..` - `:path` and `:filename` support `{{...}}` interpolation from resolved step params (`input.*`, `data.*`, `query.*`, etc.) plus runtime fields like `workspace-id`, `flow-slug`, and `step-id` - Existing slash-bearing `:filename` flows still work, but new flows should prefer the explicit `:path` + `:filename` split ## Installer-Configured Storage Scopes When installers should control where persisted artifacts land, declare a `:blob-storage` slot and point `:persist :slot` at that slot. For connected persists, the installer-configured storage root becomes the write base under the runtime workspace: ```text workspaces//storage/// ``` That is the full platform path shape for connected persists. Breyta does not add hidden `//` segments after the configured storage root. Author the slot once: ```clojure {:requires [{:slot :archive :type :blob-storage :label "Archive storage" :config {:prefix {:default "reports" :label "Folder prefix" :description "Stored under this folder in the selected storage connection." :placeholder "reports/customer-a"}}}]} ``` Use it from `:persist`: ```clojure (flow/step :http :download-report {:connection :reports-api :path "/exports/latest" :response-as :bytes :persist {:type :blob :slot :archive :path "{{input.tenant-id}}/{{input.run-date}}" :filename "summary-{{input.report-id}}.pdf"}}) ``` With storage root `reports/customer-a`, that write lands at: ```text workspaces//storage/reports/customer-a///summary-.pdf ``` Use the same slot from a runtime resource picker: ```clojure {:invocations {:default {:inputs [{:name :report :label "Archived report" :type :resource :slot :archive :accept ["application/pdf"]}]}}} ``` Notes: - installer-owned `:blob-storage` slots add a required setup control for the storage root - authors can customize the default/prefix with `:config {:prefix ...}`, but cannot disable setup - `:persist :path` stays relative to the configured root - resource pickers reuse the same resolved connection/root, so authors do not wire the prefix twice - end-user installations default to isolated roots; shared roots require explicit installer configuration - persisted blob resources are canonical `:file` resources - prefer binding invocation resource inputs by `:slot` ## End-To-End Producer And Consumer Example Use this pattern when Flow A writes files and Flow B later works on those files. For example, an influencer research flow can write a retained CSV to a private installer-scoped folder, and an outreach flow can let the same user pick that CSV from the run form resource picker instead of downloading and uploading CSV files manually. Producer flow: ```clojure {:requires [{:slot :archive :type :blob-storage :label "Archive storage" :config {:prefix {:default "reports" :label "Folder prefix"}}}] :flow '(let [download (flow/step :http :download-report {:connection :reports-api :response-as :bytes :persist {:type :blob :slot :archive :path "{{input.customer-id}}/{{input.run-date}}" :filename "summary-{{input.report-id}}.pdf"}})] {:download download})} ``` Consumer flow: ```clojure {:requires [{:slot :archive :type :blob-storage :label "Archive storage" :prefers [{:flow :report-producer :slot :archive}] :config {:prefix {:default "reports" :label "Folder prefix"}}}] :invocations {:default {:inputs [{:name :report :label "Archived report" :type :resource :slot :archive :accept ["application/pdf"]}]}} :flow '(let [input (flow/input)] {:report (:report input)})} ``` By default, two end-user installations stay isolated because each one derives its own private root, such as `installations//reports` and `installations//reports`. They share only if both installations are explicitly configured with the same root: ```clojure {:bindings {:archive {:binding-type :connection :connection-id "platform" :config {:root "reports/acme"}}}} ``` and the producer run uses: ```clojure {:customer-id "cust-77" :run-date "2026-03-24" :report-id "rep-42"} ``` the stored object path is: ```text workspaces//storage/reports/acme/cust-77/2026-03-24/summary-rep-42.pdf ``` The consumer does not need to know that path. Its runtime picker simply scopes to the same concrete storage location behind `:slot :archive`. For public UX, prefer this resource picker handoff when a downstream flow should reuse an artifact from a prior run. Keep manual upload as a fallback, but do not make users browse all workspace resources or copy `res://` values by hand. If you know one producer flow is the intended upstream lane, add `:prefers` to the consumer slot. That records the intended sharing relationship, but it does not auto-select or persist the consumer root. To share, the installer still must explicitly save the same `connection + root` on both installations. Keep these boundaries in mind: - the producer writes through its own local installer-owned slot such as `:archive` - the consumer reads through its own local installer-owned slot such as `:archive` - the installer decides whether those slots share by choosing the same or different storage roots - if two flows point at the same storage location, they share both the utility and the overwrite risk ## Resource Types Use the resource-type split like this: - `:persist {:type :blob ...}` creates `:file` resources - uploads create `:file` resources - `:persist {:type :kv ...}` creates `:result` resources - `:persist {:type :table ...}` creates `:result` resources backed by the `:persist-table` adapter - captured run and step outputs stay `:result` Because persisted blobs and uploads are both `:file`, most resource picker fields can omit `:resource-types` entirely. Add `:resource-types` only when you need something narrower than the default file picker, for example `[:result]` for structured run outputs or persisted table resources. ## Table Resources Table resources are persisted results with a bounded table-like query/edit surface. In the resource panel, partitioned table families render as grouped table resources: - the family remains the primary resource identity - when `tablePartition` is omitted, the panel defaults to the newest `:date-bucket` table or the first bounded table for other strategies - if an explicit `tablePartition` is missing, the panel shows a clear warning instead of silently falling back - the panel itself is read-only; use `breyta resources table ...` or `flow/step :table` for imports and mutations - the panel keeps CSV export for the currently previewed table - the panel keeps `Copy Markdown` for the currently visible preview page - table selection rerenders in place inside the current panel or sidepeek - most family metadata sits behind a compact info tooltip so the preview stays focused on rows and columns On run pages, table resource refs open the primary table preview by default. Use `artifactUri=...` for another resource. Human-readable table output should include the persisted table ref. Inline `:rows` / `:columns` / `:schema` / `:query` maps are not table resources. Create on first write: ```clojure (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] :indexes [{:field :status} {:field :customer-id}]}}) ``` Use the dedicated `:table` step later: ```clojure (flow/step :table :open-orders {:op :query :table {:ref orders-ref} :where [[:status := "open"]] :sort [[:order-id :asc]] :page {:mode :offset :limit 25 :offset 0}}) ``` Query paging contract: - `:page` is required for `:table {:op :query ...}` - `:table {:ref }` is canonical; bare refs work for simple reads. - `:page.mode` must be explicit as `:offset` or `:cursor` - cursor paging requires explicit `:sort` - the first cursor page omits `:page.cursor` You can also author or evolve logical columns later: ```clojure (flow/step :table :define-customer-name {:op :set-column :table {:ref orders-ref} :column :customer-name :definition {:semantic-type :text :computed {:type :lookup :reference-column :customer-id :field :name}}}) ``` `set-column` backfills bounded tables. Use `:recompute` only to rerun derived/reference values. For partitioned families, pass partition scope on `:table`. Dynamic enum columns keep stored values stable while letting authors control rendered labels: ```clojure (flow/step :table :define-status-enum {:op :set-column :table {:ref orders-ref} :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: - `:enum` implies `type-hint "enum"` - writes, `:update-cell`, CSV import, and `:recompute` normalize 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, `:query`, `:get-row`, and CSV export keep the normalized ids - the web table preview and `Copy Markdown` render enum names instead of raw ids Display formatting is render-only: - column `:format` metadata and sparse `:update-cell-format` overrides can render `relative-time`, `date`, `timestamp` / `date-time`, and `currency` - the web table preview and `Copy Markdown` apply those formats to the currently visible page - CLI/API query surfaces and CSV export keep canonical raw values Resource refs are also first-class cell values: - store canonical `{:type :resource-ref :uri ...}` maps in row data when a cell should point at another resource - the web table preview renders those cells as clickable resource chips and opens the target resource in the same panel or sidepeek - `Copy Markdown` uses the rendered label for the currently visible page - CLI/API query surfaces and CSV export keep the canonical raw resource-ref value Table resources can also be used as invocation inputs. Declare a `:resource` input filtered to `:result` resources and, when you want only persisted tables, add the table MIME type: ```clojure {:invocations {:default {:inputs [{:name :source-table :label "Source table" :type :resource :resource-types [:result] :accept ["application/vnd.breyta.table+json"]}]}} :flow '(let [input (flow/input) preview (flow/step :table :preview-source {:op :query :table (:source-table input) :page {:mode :offset :limit 25}})] {:source-table (:source-table input) :preview preview})} ``` Important boundaries: - paged by default - query-like operations stay bounded and scoped to one table or an explicit bounded partition subset - no implicit all-partitions scans from the family root - joins only through bounded `:materialize-join` - no raw SQL - no cross-workspace reads Key v1 table-family limits: - `500` table resources (families) per workspace - `50_000` live rows per concrete table inside a family - `200` columns per table - `16` promoted/index fields per table - `128` partitions per family - `16` partitions touched per write - `12` selected partitions per query/aggregate/export - `24` selected partitions per preview/read/schema - `256` max partition key bytes - `64 KB` max cell size - `256 KB` max row payload - `256 MB` max table size - `2 GB` max workspace table DB size - `1_000` rows per write - `1_000` rows per query page - `10_000` max query scan window via `page.offset + page.limit` - `200` max aggregate groups If you need materially more than that, use a dedicated database/query backend instead of expanding table-resource workarounds flow-by-flow. Current design guidance when one logical dataset approaches bounded-table limits: - keep `50_000` live rows per concrete table or partition as a real boundary - use first-class `:partitioning` when 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 instead of expecting implicit all-partitions scans - use separate explicit tables when the data truly represents different datasets or lifecycles, not just as a workaround for missing partition support - if the workload mainly needs wide cross-partition scans, arbitrary joins, or general database behavior, prefer a dedicated `:db` step and external database/query backend ## Reading Persisted Content ```bash breyta resources workflow list breyta resources read breyta resources search "transcript" --limit 10 ``` Commands that help: - `breyta resources search "" [--limit 10] [--type result|file] [--content-sources file,result]` - `breyta resources search "" [--limit 10] [--storage-backend gcs] [--storage-root reports/acme] [--path-prefix exports/2026]` - `breyta resources list [--types file] [--storage-root reports/acme] [--path-prefix exports/2026]` - `breyta resources workflow step ` - `breyta resources get ` - `breyta resources read [--limit 100] [--offset 0]` - `breyta resources table query --page-mode offset --limit 100` - `breyta resources table query --page-mode cursor --sort-json '[[\"order-id\",\"asc\"]]'` - `breyta resources table get-row --row-id ` or `--key order-id=ord-1` - `breyta resources table get-row --key meeting-key=m1 --key agenda-item-number=1` - `breyta resources table aggregate --group-by currency --metrics-json '[...]'` - `breyta resources table aggregate --group-by-json '[...]' --metrics-json '[...]'` - `breyta resources table schema ` - `breyta resources table export [--out orders.csv]` - `breyta resources table import --file orders.csv --write-mode append|upsert` - `breyta resources table import orders-import --file orders.csv --write-mode upsert --key-fields order-id [--index-fields status]` - `breyta resources table update-cell --key order-id=ord-1 --column status --value closed` - `breyta resources table update-cell-format --key order-id=ord-1 --column amount --format-json '{\"display\":\"currency\",\"currency\":\"USD\"}'` - `breyta resources table set-column --column customer-name --computed-json '{...}'` - `breyta resources table set-column --column status --enum-json '{...}'` - `breyta resources table recompute --limit 1000 --offset 0` - `breyta resources url ` For blobs, `resources read` returns a compact content preview by default. For table URIs, it returns a bounded preview page and pagination metadata. Use `--full` only when the complete payload is required. Switch to `resources table ...` when you need the richer query, export, import, aggregate, or single-cell edit surface. For enum columns, CLI/API query and export surfaces return the stored normalized ids; the web table preview and `Copy Markdown` render the configured names. For partitioned families, single-cell edits stay within the selected partition and cannot change the partition-driving field; use a normal write/upsert when a row should land in a different table. The bounded aggregate surface also supports: - group ordering via `order-by-json` - truncation visibility via `hasMore` - metric-local filters via `where` - scalar `arg-max` / `arg-min` metrics for "latest/highest row value per group" cases - `having-json` for post-group filtering - bounded `collect-set` metrics - `group-by-json` for date bucket and numeric-bin specs - `percentile` and `median` for bounded distribution reporting Use storage filters like this: - `storage-backend` narrows by backend family, such as `gcs` - `storage-root` narrows to the installer-configured root, such as `reports/acme` - `path-prefix` narrows further inside that root, relative to it, such as `exports/2026` - `path-prefix` is relative to the configured root, not the full `workspaces//storage/...` object path That means a platform-backed persisted file stored at: `workspaces//storage/reports/acme/exports/2026/summary.pdf` is searchable with: - `--storage-backend gcs` - `--storage-root reports/acme` - `--path-prefix exports/2026` ## Resource Search Indexing How persisted artifacts become searchable in `breyta resources search`: - search indexes metadata fields (`display name`, URI/path context, tags, source label) - connected persists also index normalized storage scope fields so search and pickers can filter by backend, root, and relative path - text content indexing is enabled only for text-like payloads - `:tier :ephemeral` blobs are metadata-indexed by default (raw content is not extracted) - binary blobs are discoverable by metadata/path context, but raw binary content is not full-text indexed - indexed text is bounded by size/character limits for stability For connected persists, Breyta stores both the full path and normalized storage fields: | Indexed field | Meaning | Example | | --- | --- | --- | | `path` | Full physical object path, useful for broad search/debug context | `workspaces//storage/reports/acme/exports/2026/summary.pdf` | | `storage_backend` | Backend family | `platform` | | `storage_root` | Installer-configured root inside that backend | `reports/acme` | | `path_under_root` | Relative path below the root | `exports/2026/summary.pdf` | That split is intentional: - free-text search can still match the full `path` - `storage-root` and `path-prefix` use the normalized fields instead of requiring the full workspace storage path - the same backend/root/relative-path contract can extend to future storage backends without changing authored filters ### `:persist :search-index` Overrides Use `:search-index` under `:persist` to customize indexed text/metadata for persisted artifacts (especially binary blobs), without changing stored payload bytes. Target shape: ```clojure {:persist {:type :blob :path "invoices/{{input.invoice.customer-id}}" :filename "invoice.pdf" :search-index {:text "invoice-id=INV-123 vendor=Acme total=4500" :tags ["invoice" "acme" "emea"] :source-label "Invoice PDF from SAP import" :include-raw-content? false}}} ``` Intended precedence: - `:search-index.text` overrides default indexed content text - `:search-index.tags` overrides/augments indexed tags - `:search-index.source-label` overrides derived source label - `:search-index.include-raw-content?` controls whether default extracted text is also included when available Find the same persisted artifact later with `flow/step :search`: ```clojure '(let [artifact (flow/step :function :persist-invoice {:input {:invoice invoice} :code '(fn [{:keys [invoice]}] invoice) :persist {:type :blob :path "invoices/{{input.invoice.customer-id}}" :filename "invoice.json" :content-type "application/json" :search-index {:text "invoice-id=INV-123 vendor=Acme total=4500" :tags ["invoice" "acme" "emea"] :source-label "Invoice bundle for Acme" :include-raw-content? true}}}) hits (flow/step :search :find-invoice {:query "invoice-id=INV-123" :targets [:resources] :limit 5 :hydrate {:enabled true :top-k 1 :max-chars 12000}})] {:artifact artifact :hits hits}) ``` Quick operator/debug loop: ```bash breyta resources search "invoice-id=INV-123" --limit 10 ``` ## Open The Same Artifact In Breyta Web In API mode JSON output, resource responses can include optional `webUrl` links that point to the artifact context in Breyta Web: - `breyta resources workflow list --format json` -> `data.items[].webUrl` (and `meta.webUrl` for a primary destination) - `breyta resources search "" --format json` -> `data.items[].webUrl` and `data.items[].displayName` - `breyta resources get --format json` -> `data.webUrl` (and usually `meta.webUrl`) - `breyta resources url --format json` -> signed `data.url` plus optional `data.webUrl`/`meta.webUrl` Quick extraction pattern: ```bash breyta resources get --format json | jq -r '.meta.webUrl // .data.webUrl // empty' ``` ## Signed URLs vs public preview links `breyta resources url ` returns a temporary signed URL for direct resource access. It is useful for operators, runtime handoff, and debugging, but it is not the production outreach/share mechanism for external viewers. Use public artifact preview links when someone should open a read-only artifact without logging in: ```http POST /api/resources/shares GET /public/artifact-previews/:token GET /public/artifact-previews/:token/download DELETE /api/resources/shares/:token ``` Send `X-Breyta-Workspace: ` on the authenticated create and revoke API calls. Public preview links are unlisted, revocable, optionally expiring, and render a sanitized artifact page. The page hides workspace/run/debug metadata, raw resource refs, private resource-content proxy URLs, common signed storage URLs, and private resource actions. Use this for creator outreach or external review flows where the recipient should see the opportunity or report, not the workspace internals. When the share request sets `allowDownload: true` and the shared artifact is text-like, such as EDN, Markdown, CSV, JSON, XML, JavaScript, form data, or plain text, the share response includes a token-scoped `publicDownloadUrl`. That route serves a bounded attachment through the same revocable/expiring token. It does not expose private resource-content URLs or signed storage URLs, and it does not serve binary media or sandboxed HTML previews. ## Cross-Flow State Handoff With KV For shared state between runs/flows, pair result persistence with KV writes: 1. persist large step output as `res://...` 2. write a compact KV record that points to that ref 3. read KV in downstream flows and resolve ref only when needed ```clojure '(let [payload (flow/step :http :collect {:connection :source-api :method :get :path "/records" :persist {:type :blob}}) _kv (flow/step :kv :record-latest {:operation :set :key "records:latest" :value {:uri (:uri payload)} :ttl 604800}) latest (flow/step :kv :load-latest {:operation :get :key "records:latest"})] {:latest (:value latest)}) ``` This keeps orchestration payloads small while still giving operators a durable pointer to the latest artifact. ## Design Rules - persist early when output size is uncertain - return refs instead of heavy payloads in final output - pass refs explicitly; don’t hide them in nested structures - treat persisted artifacts as durable run history - persist when payloads can grow, are reused across steps, or need operator inspection after completion - use `:persist {:type :table ...}` when row-shaped data should stay queryable/editable as a resource later - for cross-run/cross-flow lookup, store lightweight pointers in KV instead of duplicating large objects ## Troubleshooting - downstream steps fail with large payloads: persist the producer output and re-run - resource not found: list workflow resources and match `step-id` to `workflowId` - `resources` commands require authenticated API mode - debug persisted refs by listing workflow resources, finding the producing `step-id`, and reading the target `res://` URI ## Related - [Step Table](/docs/reference-step-table) - [Templates](/docs/reference-templates) - [Runs And Outputs](/docs/operate-runs-and-outputs) - [Advanced Multi-Feature Flow](/docs/example-advanced-multi-feature) --- Document: Webhooks And Secret Refs URL: https://flows.breyta.ai/docs/guide-webhooks-and-secret-refs.md HTML: https://flows.breyta.ai/docs/guide-webhooks-and-secret-refs Last updated: 2026-05-11T10:30:49+02:00 # Webhooks And Secret Refs Breyta webhook security guide for authenticated ingestion with target-bound secrets. ## Goal Run webhook-interface flows safely with explicit auth config, secret refs, and predictable payload constraints. ## Quick Answer Use `:invocations` for the callable webhook payload, then expose webhook ingress with `:interfaces {:webhook [...]}`. Bind secret material through target bindings. ## Canonical Interface Shape ```clojure {:requires [{:slot :webhook-signing-secret :type :secret :label "Webhook Signing Secret"}] :invocations {:order-updated {:inputs [{:name :order-id :type :text :label "Order ID" :required true}]}} :interfaces {:webhook [{:id :order-webhook :invocation :order-updated :enabled true :event-name "orders.updated" :description "Receive order update webhooks." :auth {:type :hmac-sha256 :header "Stripe-Signature" :secret-ref :webhook-signing-secret}}]}} ``` Notes: - older event-style webhook definitions are still accepted for existing flows - webhook paths are generated by Breyta; inspect the generated interface endpoint for the target you are testing instead of hardcoding `:path` in the flow source - the interface details panel shows the generated webhook path and whether the referenced secret binding is configured - author draft/live endpoints are flow-source scoped: `/api/flows/{flow-slug}/interfaces/draft/{interface-id}` and `/api/flows/{flow-slug}/interfaces/live/{interface-id}` - installed consumer endpoints are installation scoped: `/api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}` - workspace-scoped interface endpoint forms remain available as alternate compatibility URLs when you need an explicit workspace id in the path - legacy `:config :fields` webhook payload validation is still accepted for existing flows, but new payload contracts should be expressed as invocation inputs ## Secret Slot Setup The `:requires` secret slot in the canonical shape above declares where the webhook signing secret is bound. Bind secret values through flow configuration; never hardcode secret strings in flow files. ## Auth Modes - `:hmac-sha256` for providers like Stripe/GitHub - `:signature` for generic signed payload schemes (HMAC/ECDSA) - `:bearer` and `:api-key` for token/header auth - `:none` should be avoided on public webhook endpoints GitHub `X-Hub-Signature-256` example: ```clojure {:auth {:type :signature :algo :hmac-sha256 :signature-header "X-Hub-Signature-256" :signed-message :payload :signature-format :hex :signature-prefix "sha256=" :secret-ref :github-webhook-secret}} ``` GitHub signs the exact raw request body and sends the hex digest with a `sha256=` prefix. ## Payload Limits That Matter - webhook total payload max: 50 MB - signed multipart webhook payload max: 5 MB - raw MIME field payload max: 50 MB ## Rotation And Operations - rotate by updating bound secret values, then rerun with the target (`flows run --target live` when validating live behavior) - if replay detection trips, ensure providers send unique signatures/event ids - preserve raw request body for signature verification paths ## Draft Vs Live Webhook Behavior Webhook behavior follows runtime target selection: | Aspect | `draft` | `live` | |---|---|---| | Intended usage | Dev/staging-style iteration while authoring. | Stable installed runtime ingress for consumers. | | Definition/config source | Latest pushed working copy + draft target configuration. | Installed released version + live/installation configuration. | | Endpoint stability expectation | Can change frequently during iteration. | Treated as stable runtime surface between controlled release/promote events. | | Endpoint shape | Flow-source scoped with `draft` in the path. | Flow-source scoped with `live` in the path for author testing; installed consumer endpoints stay installation scoped. | Guidelines: - Do not derive webhook URLs from slug/event-name assumptions. - Treat server-returned interface endpoints as authoritative for the target you are testing. - For installation/live ingress, inspect interface metadata with `breyta flows installations interfaces `. - For keyed concurrency, choose a `:key-field` that exists in the raw incoming webhook payload before the flow body normalizes input. For GitHub pull request events, prefer `:number` or `[:pull_request :id]` over a later derived request key. ## Runtime Surface Strategy By Target Use runtime surfaces differently across targets: | Target | Preferred runtime surface during development/operations | Why | |---|---|---| | `draft` | Manual interface runs first; optional webhook testing via generated draft interface endpoint | Fast iteration and deterministic debugging. | | `live` | Top-level schedules for unattended jobs, plus installation/live webhook interfaces | Stable, continuous runtime behavior for consumers. | Draft webhook interface endpoints are workspace-authenticated and source scoped: - `POST /api/flows/{flow-slug}/interfaces/draft/{interface-id}` Legacy draft-event routes are still accepted for older tooling, but new clients should use the generated interface endpoint. Example: ```bash curl -X POST "$BREYTA_API_URL/api/flows/orders/interfaces/draft/orders-updated" \ -H "Authorization: Bearer $BREYTA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"orderId":"ord_123"}' ``` ## Common Pitfalls - missing `:auth` on webhook interface -> auth error at runtime - `:auth` present but missing `:type` -> validation error - signature auth without raw body preservation -> verification fails ## Frequently Asked Questions ### Should webhook auth live in interface config or in `:requires`? Both. Interface config declares auth behavior; `:requires` declares where secret material is bound. ### Can I keep webhooks unauthenticated in production? No. Public webhooks should use authenticated interface config. ## Related - [Flow Configuration](/docs/guide-flow-configuration) - [Flow Definition](/docs/reference-flow-definition#webhook-interfaces) - [Waits, Signals, And Timeouts](/docs/guide-waits-signals-and-timeouts) - [Limits And Recovery](/docs/reference-limits-and-recovery) --- Document: Flow Features And Config Toggles URL: https://flows.breyta.ai/docs/guide-flow-features-and-config-toggles.md HTML: https://flows.breyta.ai/docs/guide-flow-features-and-config-toggles Last updated: 2026-05-20T03:00:25+02:00 # 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](/docs/reference-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`. ```clojure '(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: - [Orchestration Constructs](/docs/reference-orchestration-constructs) - [flow/poll](/docs/reference-flow-poll) 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: ```clojure '(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: ```clojure '(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: ```clojure '(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: ```clojure '(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: - `:enum` implies `type-hint "enum"` - writes, `:update-cell`, CSV import, and `:recompute` normalize 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 `:format` metadata and sparse `:update-cell-format` overrides can render `relative-time`, `date`, `timestamp` / `date-time`, and `currency` - the web preview and `Copy Markdown` apply 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: ```clojure '(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 - `:left` and `:inner` only - 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 `:append` or `:upsert` - no snapshot/replace mode yet - joins read the current materialized row state of source tables - run `:recompute` first 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: - `500` table resources (families) per workspace - `50_000` live rows per concrete table inside a family - `200` columns per table - `16` promoted/index fields per table - `128` partitions per family - `16` partitions touched per write - `12` selected partitions per query/aggregate/export - `24` selected partitions per preview/read/schema - `256` max partition key bytes - `64 KB` per cell - `256 KB` max row payload - `256 MB` max table size - `2 GB` max workspace table DB size - `1000` rows per write - `1000` rows per query page - `10000` max query scan window via `page.offset + page.limit` - `200` max aggregate groups Bounded aggregate reporting now also supports: - `order-by` on group keys or metric aliases - `has-more` truncation metadata - metric-local conditional filters - scalar `arg-max` / `arg-min` - `having` on grouped results - bounded `collect-set` - date-bucket group specs for `day`, `week`, and `month` - numeric-bin group specs - `percentile` and `median` distribution metrics Current recommendation when one logical dataset approaches bounded-table limits: - keep `50_000` live rows per concrete table or partition as a real boundary - use first-class `:partitioning` when 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 `:db` step 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](/docs/reference-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 --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. | ## Related - [Flow Definition](/docs/reference-flow-definition) - [Orchestration Constructs](/docs/reference-orchestration-constructs) - [flow/poll](/docs/reference-flow-poll) - [Step Table](/docs/reference-step-table) - [Templates](/docs/reference-templates) - [Webhooks And Secret Refs](/docs/guide-webhooks-and-secret-refs) - [Waits, Signals, And Timeouts](/docs/guide-waits-signals-and-timeouts) - [Notifications And Email](/docs/guide-notifications-and-email) --- Document: Failure-Mode Cookbook URL: https://flows.breyta.ai/docs/guide-failure-mode-cookbook.md HTML: https://flows.breyta.ai/docs/guide-failure-mode-cookbook Last updated: 2026-05-19T09:54:50+02:00 # Failure-Mode Cookbook Practical failure signatures and fixes for advanced flow configs (child orchestration, ref loading, waits, webhook auth, notifications). ## Quick Answer Use this as a triage map: match the error signature, apply the canonical fix, and rerun at step scope before full flow reruns. ## Child Flow And Profile Context Failures | Error signature | Why it happens | Canonical fix | |---|---|---| | `no installation or live target context` | Child flow needs bound `:requires` context, but the parent run did not carry an installation/live target context. | Same-app children inherit context from a linked public install. For other child calls, invoke with `--target live`, `:installation-id`, or `:profile-id`; use KV handoff when target propagation is unclear. | ## Ref-Load And Size Failures | Error signature | Why it happens | Canonical fix | |---|---|---| | `Blob reference too large for ...` | Ref exceeds context-specific load cap. | Keep LLM refs compact; keep code refs under limits; stream/pre-split payloads for HTTP body refs. | | `Code step :input too large` | Total function input exceeds code input cap. | Pass only required fields; move heavy transforms to DB/warehouse/external compute; orchestrate smaller batches. | ## Wait/Approval Failures | Error signature | Why it happens | Canonical fix | |---|---|---| | `Wait timeout exceeds maximum` | Configured timeout is above allowed max. | Use shorter bounded waits (`"2h"`, `"1d"`); chain waits for long multi-stage processes. | | Runs repeatedly timing out at wait | Completion signal is missing or timeout policy is weak. | Ensure deterministic wait key/signal path; set explicit `:on-timeout`; include operator context in `:notify`. | ## Webhook/Auth Failures | Error signature | Why it happens | Canonical fix | |---|---|---| | `Webhook authentication required` | Webhook interface is missing valid auth config. | Set interface auth (`:interfaces {:webhook [{:auth {:type ...}}]}`); bind required secret/public-key refs in the target bindings. Older webhook definitions remain compatibility-only. | | `Signature authentication requires raw request body` | Signature verification cannot run on parsed-only payload. | Preserve raw request body in ingress; keep signature/timestamp header mapping correct. | | `Webhook replay detected` | Duplicate delivery/signature within replay window. | Use provider idempotency keys/event IDs; ensure retries send unique delivery metadata where possible. | ## Notification Failures | Error signature | Why it happens | Canonical fix | |---|---|---| | `:channels is required for notify step` | Notify step executed without channels payload. | Provide at least one `:http` channel; validate notify payload in step isolation before full run. | | partial channel delivery (`:all-sent? false`) | One or more channels failed while others succeeded. | Inspect per-channel result map; add retry/compensation around notify; keep payload bounded and template variables explicit. | ## Operational Triage Loop | Step | Action | |---|---| | 1 | Isolate failing step with `breyta steps run` when possible. | | 2 | Apply minimal config fix from this cookbook. | | 3 | Rerun step in isolation. | | 4 | Rerun full flow with `breyta flows run --wait`. | ## Related - [Limits And Recovery](/docs/reference-limits-and-recovery) - [Waits, Signals, And Timeouts](/docs/guide-waits-signals-and-timeouts) - [Webhooks And Secret Refs](/docs/guide-webhooks-and-secret-refs) - [Notifications And Email](/docs/guide-notifications-and-email) --- Document: Limits And Recovery URL: https://flows.breyta.ai/docs/reference-limits-and-recovery.md HTML: https://flows.breyta.ai/docs/reference-limits-and-recovery Last updated: 2026-05-20T03:00:25+02:00 # Limits And Recovery Canonical runtime and payload limits for public flow authoring, plus recovery actions for common limit failures. ## Quick Answer Design flows to stay within hard limits, and prefer templates/persistence/chunking before raising payload size in runtime paths. ## Core Flow Limits | Limit | Value | Notes | |---|---|---| | Max flow payload size | 150 KB | Definition map only; excludes template/function bodies. | | Max steps per flow | 50 | Total declared steps in a flow. | | Max `flow/call-flow` nesting depth | 5 | Child-flow nesting guardrail. | | Max templates total size | 1 MB | Aggregate template content budget. | | Max functions total size | 1 MB | Aggregate function content budget. | | Effective package budget | ~2.1 MB | Combined payload + templates + functions. | ## Runtime Limits Per Run | Limit | Value | Notes | |---|---|---| | Max step executions | 10000 | Per run execution ceiling. | | Max HTTP requests | 50 | Per run, across HTTP steps. | | Max tracked LLM tokens per run (platform-managed keys) | 100000 | Accounting limit for platform-managed keys. | | Platform-managed `:max-tokens` clamp per LLM call | 10000 | Applied only for platform-managed keys. | | User-provided LLM keys `:max-tokens` clamp | Not clamped by platform | Provider/account limits still apply. | | Max workflow duration | 30 days | Hard run duration cap. | | Max child flow invocations | 25 | `flow/call-flow` invocations per run. | | Max fanout step executions | 5 | Total `:fanout` step executions per run. | | Max fanout total items | 100 | Total items across all `:fanout` steps in one run. | ## Fanout And Child-Workflow Guardrails | Guardrail | Value | Notes | |---|---|---| | Max fanout width | 25 | Max items in one `:fanout` step. | | Max async child fanout concurrency | 5 | `:max-concurrency` must be at most `5`; validation rejects larger values. | | Async fanout item type | `:call-flow` only | All items in async mode must be child workflow items. | | Mixed fanout modes | Not allowed | Do not mix legacy step items and `:call-flow` items in one `:fanout`. | | Nested fanout items | Not allowed | `:fanout` items cannot themselves be `:fanout`. | | Nested async child fanout depth | 1 | Descendant async child fanout is rejected. | | Child start confirmation | Required | Parent waits for child start confirmation before treating spawn as successful. | ## Wait And Approval Limits | Limit | Value | Notes | |---|---|---| | Default wait timeout | 24h | Applies when timeout is omitted. | | Max wait timeout | 30 days | Hard cap for wait duration. | | Checkpoint timeout default | 24h | Review checkpoint timeout can be set up to 30 days. | ## Polling Limits (`flow/poll`) | Limit / Rule | Value | Notes | |---|---|---| | Max poll timeout | 1h | Hard timeout ceiling for `flow/poll`. | | Max poll attempts | 100 | Hard attempt ceiling for `flow/poll`. | | Required bound | At least one of `:timeout` or `:max-attempts` | Unbounded polling is rejected. | | Required condition + backoff validation | `:return-on` required | Backoff shape is validated (for example `:linear` requires `:step`). | ## Step Result And Persistence Limits | Limit | Value | Notes | |---|---|---| | Inline result threshold | 512 KB | Results above this should usually use `:persist`. | | Max step result size | 1 MB | Hard per-step output cap. | | DB result max bytes | 1 MB | Cap for database step result payload bytes. | | DB result max rows | 5000 | Cap for row count returned by DB steps. | | Code step input max | 1 MB | Hard cap on `:function` step `:input`. | | Notify payload max | 1 MB | Hard cap for notify payload body. | | Authoring recommendation | Prefer `:persist` for unknown/growing outputs | Many real flows exceed inline limits quickly. | ## Webhook And Ref Loading Limits | Limit | Value | Notes | |---|---|---| | Webhook payload max | 50 MB | General webhook body size ceiling. | | Signed multipart webhook payload max | 5 MB | Applies to signed multipart uploads. | | Raw MIME webhook payload max | 50 MB | Raw MIME ingestion ceiling. | | Ref load into LLM content | 100 KB | Ref content cap when injected into LLM fields. | | Ref load into code step | 1 MB | Ref content cap for code step loading. | | Ref load into HTTP body | 10 MB retained / 20 MB ephemeral tier | Tier-dependent HTTP body ref loading limit. | | Persist write cap | 50 MB retained / 4 GB ephemeral tier | Tier-dependent blob persistence write limit. | ## Config Warning Thresholds | Threshold | Behavior | Recommendation | |---|---|---| | ~1 KB per step payload | Non-blocking warning starts around this size | Move large literals into templates. | ## Failure Recovery Patterns When you hit size/limit validation errors, apply these patterns first: | Validation error | First recovery actions | |---|---| | `Flow payload too large` | Move large inline content into `:templates` and `:functions`.
Replace large literals in `:flow` with template/function refs. | | `Result too large` / `Query result too large` / `DB row limit exceeded` | Paginate (`LIMIT`, cursor loops).
Aggregate/filter in database first.
Persist artifacts and pass refs instead of full payloads. | | `Blob reference too large for ...` | Reduce blob size before loading it into a step.
For user downloads, return signed links instead of loading full content into flow memory. | | `Code step :input too large` | Pass compact subsets into code steps.
Move heavy processing to data layer or external compute.
Orchestrate in smaller batches. | | `Fanout width exceeds limit` / `max fanout total items exceeded` | Reduce batch size.
Split work into multiple parent runs or stages.
Keep one `:fanout` bounded to independent child workflows. | | `child-flows limit exceeded` / `Nested async child fanout is not allowed` | Keep async child fanout at one orchestration depth.
Move deeper branching inside a single child flow.
Use plain `flow/call-flow` for descendant orchestration. | | `Wait timeout exceeds maximum` | Use shorter waits and multi-stage waits.
Branch timeout outcomes explicitly (`:fail` or `:continue`). | | `flow/poll timeout exceeds limit` / `flow/poll max attempts exceeded` | Reduce timeout and use staged polling (fast first poll, slower secondary poll).
Increase interval and use explicit backoff.
Prefer webhook/wait-signal patterns when callbacks are supported. | ## Recommended Authoring Checklist | Checklist item | Why | |---|---| | Add `:persist` when output size is uncertain | Prevents inline payload overflow as data grows. | | Use templates for large request/prompt/SQL bodies | Keeps flow payload size stable and reviewable. | | Keep waits bounded and keyed | Avoids unbounded waits and ambiguous resume paths. | | Design webhook auth explicitly (`:auth {:type ...}`) | Prevents insecure/default webhook exposure. | | Test largest expected payload paths, not just tiny happy-path samples | Catches real-world limit failures before production. | ## Related - [Persisted Results And Resource Refs](/docs/guide-persisted-results-and-resources) - [Webhooks And Secret Refs](/docs/guide-webhooks-and-secret-refs) - [Waits, Signals, And Timeouts](/docs/guide-waits-signals-and-timeouts) - [flow/poll](/docs/reference-flow-poll) --- Document: Notifications And Email URL: https://flows.breyta.ai/docs/guide-notifications-and-email.md HTML: https://flows.breyta.ai/docs/guide-notifications-and-email Last updated: 2026-05-07T21:40:28+02:00 # Notifications And Email Breyta notification guide for `:notify` and wait notifications, including email delivery via HTTP channels. ## Goal Send operational notifications and email alerts from flows using canonical notify config. ## Quick Answer Use `flow/step :notify` for external delivery (`:http`), and `:wait {:notify ...}` for approval notifications with action links. Workspace flow-health digests are a separate product surface: - manage them in app under `Settings -> My updates` - or from CLI with `breyta digests cadence` and `breyta digests cadence set daily|weekly|monthly` - these settings apply only to your user in the current workspace - in-app flow-health updates are on by default for your account - email flow-health updates are opt-in for your account - incident and digest operator pages are currently limited to workspace creators/admins - scheduled digests summarize surfaced incident changes from the previous `daily`, `weekly`, or `monthly` window, with `weekly` as the default - if there are no surfaced incident changes in that window, no scheduled digest is sent - urgent incident updates are delivered as soon as possible when email delivery includes urgent issues - urgent emails are not held for the next scheduled digest window ## Notify Step Pattern ```clojure (flow/step :notify :send-email {:channels {:http {:connection :sendgrid :path "/v3/mail/send" :method :post :json {:personalizations [{:to [{:email "{{recipient}}"}]}] :from {:email "alerts@example.com"} :subject "{{subject}}" :content [{:type "text/plain" :value "{{message}}"}]}}} :data {:recipient "ops@example.com" :subject "Flow completed" :message "Order {{order-id}} succeeded"}}) ``` ## Wait Notification Pattern ```clojure (flow/step :wait :approval {:key "approve:order:123" :timeout "2h" :notify {:channels {:http {:connection :sendgrid :path "/v3/mail/send" :method :post :json {:personalizations [{:to [{:email "approver@example.com"}]}] :from {:email "approvals@example.com"} :subject "Approval required" :content [{:type "text/plain" :value "Approve: {{approval-url}}\nReject: {{rejection-url}}"}]}}}}}) ``` ## Template Support - channels support Handlebars values like `{{title}}`, `{{message}}`, `{{approval-url}}` - wait notifications inject action URL variables automatically - for reusable channel payloads, define a `:notification` template in `:templates` ## Current Channel Support - `flow/step :notify`: `:http` only (SendGrid, Slack, webhooks, custom APIs) - `:wait {:notify ...}` and checkpoint prompts: optional `:http` ## Common Pitfalls - missing `:channels` in notify step -> validation error - assuming wait approvals require in-app channels -> use `:http` notifications with approval/rejection URLs - wrong connection slot in `:http` channel -> auth/connection errors - expecting native `:email` channel type -> use `:http` with an email provider API ## Related - [Templates](/docs/reference-templates) - [Waits, Signals, And Timeouts](/docs/guide-waits-signals-and-timeouts) - [Advanced Multi-Feature Flow](/docs/example-advanced-multi-feature) --- Document: CLI Commands URL: https://flows.breyta.ai/docs/reference-cli-commands.md HTML: https://flows.breyta.ai/docs/reference-cli-commands Last updated: 2026-05-19T10:42:46+02:00 # CLI Commands Reference for the canonical Breyta CLI lifecycle and docs navigation. ## Quick Answer Use this page when you need the command catalog. - Default authoring path: [Author Flows](/docs/playbook-author-flows) - Broader docs map: [Reference Index](/docs/reference-index) - New user path: [Start Here](/docs/start-here) ## Navigate By Goal | If you need | Start here | |---|---| | Default authoring loop | [Author Flows](/docs/playbook-author-flows) | | Default draft/live lifecycle | [Release And Install](/docs/playbook-release-and-install) | | Sell paid public flows | [Paid Public Flows](/docs/guide-paid-public-flows) and [Flow Metadata And Discover](/docs/reference-flow-metadata) | | Full docs map | [Reference Index](/docs/reference-index) | | Docs search query syntax | [Docs Search](REFERENCE_DOCS_SEARCH.md) | | Flow file and step contracts | [Flow Definition](/docs/reference-flow-definition) and [Step Reference](/docs/reference-step-reference) | | Flow metadata, grouping, and public discover | [Flow Metadata And Discover](/docs/reference-flow-metadata) | | Jobs workers and machine auth | [Jobs Control Plane](/docs/reference-flow-jobs) and [Service Accounts](REFERENCE_SERVICE_ACCOUNTS.md) | | Flow-health incidents and digests | [Flow Health](REFERENCE_FLOW_HEALTH.md) | | Table resources and limits | [Table Resources](REFERENCE_TABLE_RESOURCES.md) | | Connections/bindings | [Connections First](/docs/guide-connections-first), [Flow Configuration](/docs/guide-flow-configuration), [Installations](/docs/guide-installations) | | Runs/outputs/resources | [Runs And Outputs](/docs/operate-runs-and-outputs), [Persisted Resources](/docs/guide-persisted-results-and-resources), [Step Table](/docs/reference-step-table) | | Limits/recovery/reporting | [Limits And Recovery](/docs/reference-limits-and-recovery), [Troubleshooting](/docs/guide-troubleshooting), [Feedback](/docs/guide-feedback-and-reporting) | ## Core Surface | Command | What it does | |---|---| | `breyta docs` | Show docs command surface (`find`, `show`, `fields`, `sync`). | | `breyta docs find "flows"` | Search docs pages. Supports terms, quoted phrases, field filters, and boolean operators. See [Docs Search](REFERENCE_DOCS_SEARCH.md). | | `breyta docs show [--section ] [--full]` | Show compact page preview, one section, or full page. | | `breyta docs fields [field ...]` | Show compact config field rows. Add fields such as `response-as persist retry` for targeted lookup. | | `breyta docs sync --out ./.breyta-docs --clean` | Download docs corpus for offline grep/search. | | `breyta flows search ` | Search workspace flow metadata and summaries. Refine with `--step-type`, `--provider`, `--connection`, or `--flow`. | | `breyta flows grep ` | Search workspace flow source by literal pattern. Use `--or`, `--surface`, or `--scope` to narrow. | | `breyta flows templates search [--limit 5]` | Search approved templates. Use `--full` for bounded source preview. | | `breyta flows templates grep ` | Search approved template source. Use `--surface` and `--full` to narrow or expand. | | `breyta flows discover list` | Browse public installable end-user flows for the current workspace. Add `--include-own` only when debugging whether this workspace's own public flows are indexed. | | `breyta flows discover search ` | Search public installable end-user flows for the current workspace. Add `--include-own` only for owner-side indexing checks. This is separate from workspace flow search and approved template search. | | `breyta connections list [--type ...]` | List reusable workspace connections. | | `breyta connections usages [--connection-id ] [--flow ] [--only-connected]` | Show which flow targets (`draft`/`live`/`installation`) currently use each connection. | | `breyta connections items [--item-type ] [--raw]` | Inspect cached non-secret provider items used by dropdowns. | | `breyta connections create ...` | Create a new connection when no reusable one exists. | | `breyta connections test ` | Test one connection health directly. This confirms health/config state, not end-to-end step execution. | | `breyta connections test --all [--only-failing]` | Run workspace-wide connection health checks before wiring. This is a preflight check, not runtime proof. | | `breyta connections delete ` | Delete an unbound connection. If still bound, CLI returns a hint to move/unset bindings first. | | `breyta secrets list [--secret-id ]` | List workspace secret IDs. | | `breyta secrets usages [--secret-id ] [--flow ] [--only-connected]` | Show which flow targets (`draft`/`live`/`installation`) currently reference each secret. | | `breyta secrets delete ` | Delete an unbound secret. If still bound, CLI returns a hint to move/unset bindings first. | | `breyta flows list [--limit 10] [--pretty]` | List workspace flows with a compact default limit. See [Flow Metadata And Discover](/docs/reference-flow-metadata) for grouped-flow fields and metadata. | | `breyta flows show [--pretty]` | Show one flow with compact metadata, counts, step/trigger summaries, and a non-editable `flowLiteralPreview` when source is available. Use `--full` or `--include definition` for full source fields. | | `breyta flows delete --yes [--force]` | Permanently delete a flow. Use `--force` when runs/installations must be cleaned up. | | `breyta flows marketplace update --visible=true` | Enable marketplace visibility metadata after explicit author approval and installable-ready proof. Use `--visible=false` to disable. | | `breyta flows discover update --public=true` | Enable public discover visibility metadata after explicit author approval and installable-ready proof. Use `--public=false` to disable. | | `breyta flows update ...` | Update mutable flow metadata such as grouping, discover card media, display-icon selection, and publish description. | | `breyta flows pull --out ./tmp/flows/.clj` | Pull definition to local file. | | `breyta flows lint --file ./tmp/flows/.clj [--local-only\|--server]` | Lint candidate source before push. Local lint is offline and fast; server lint is canonical and non-mutating. | | `breyta flows push --file ./tmp/flows/.clj` | Push working copy. | | `breyta flows diff ` | Show draft-vs-live source diff. Draft-only flows with push history fall back to previous pushed draft vs current draft. | | `breyta flows readiness [--target draft\|live] [--public] [--marketplace]` | Compact readiness report with blockers and next commands. Defaults to draft; use live or `release-check` for release proof. | | `breyta flows release-check [--public] [--marketplace]` | Check live release readiness. Use public/marketplace flags for end-user release gates before publication or paid marketplace changes. | | `breyta flows configure suggest [--target live]` | Suggest `--set` bindings from existing connections and show unresolved slots. | | `breyta flows configure --set ...` | Configure draft runtime target (bindings/inputs) when required. For live, use `--target live` and optional `--version `. | | `breyta flows configure check ` | Check required bindings/inputs for draft target. For live, use `--target live` and optional `--version `. | | `breyta flows validate ` | Optional read-only validation (useful for CI/troubleshooting/explicit target checks). | | `breyta flows run --wait` | Start a run using draft-target bindings/config by default. Code version comes from the active release unless `--version` overrides it. | | `breyta flows run --invocation --input '{...}' --wait` | Start a specific invocation contract directly. | | `breyta flows interfaces list --target live` | List manual/HTTP/webhook/MCP interface surfaces declared over invocations. | | `breyta flows interfaces show --target live` | Inspect one interface, including invocation input metadata and endpoint details when available. | | `breyta flows metrics --source draft\|live` | Inspect redacted aggregate metrics for author-owned draft/live interface calls. Use `--installation-id ` for one installation. | | `breyta flows interfaces call --input '{...}' --wait` | Smoke-test the draft author HTTP interface route. Add `--target live` for the author live route or `--installation-id ` for a specific installation. | | `breyta flows interfaces curl --target live --input '{...}'` | Generate a copyable curl command for an HTTP interface without printing the real token. Omit `--target` for draft. | | `breyta steps run --flow --source draft --type --id --params ''` | Run one step with draft context. Output is compact and includes a binding preview. | | `breyta steps run ... --params-file ./params.json --result-path rows.0` | Run one step with larger params and focused preview. Use preview flags, `--result-file`, or `--full` to expand. | | `breyta steps record --flow --source draft --type --id --params ''` | Run one step and save observed input/output as a step example and snapshot test once behavior is stable. | | `breyta steps tests verify --source draft` | Re-run stored step tests. Use preview flags or `--full` for expansion. | | `breyta runs list --query 'status:failed flow:my-flow installation:inst-1 version:7'` | List runs with the canonical structured filter syntax. | | `breyta runs list --installation-id --version ` | List runs with legacy discrete filter flags. | | `breyta runs show ` | Inspect one run in detail. | | `breyta runs events [--step ] [--installation-id ] [--limit ]` | Inspect a bounded run and step event timeline for live debugging. | | `breyta runs inspect [--step ]` | Inspect a run with compact step data, or inspect one step's input/output/error/cost without expanding the whole run payload. | | `breyta runs step [--full] [--installation-id ]` | Short alias for compact step I/O inspection, including the step event timeline when available. | | `breyta runs continue --approve-latest-wait` | Approve the latest active approval wait for a waiting workflow, then inspect the run again with `runs show` or `runs inspect`. | | `breyta waits list [--flow ] [--workflow-id ]` | List live human-in-the-loop waits and approval requests. | | `breyta jobs create --type [--payload '{...}'] [--metadata '{...}']` | Create one queued external job. | | `breyta jobs list [--type ] [--batch-id ] [--status ] [--limit ]` | List jobs with optional type, batch, status, and limit filters. | | `breyta jobs show ` | Show durable job state and persisted outputs/artifacts. | | `breyta service-accounts list [--status active\|disabled]` | List workspace service accounts. | | `breyta service-accounts create --name --scope [,] [--scope ] [--job-type ]` | Create a workspace service account with explicit scopes. `--scope` accepts repeated flags or comma-separated values. `--capability` remains a compatibility alias. | | `breyta service-accounts keys create --name ` | Mint a service-account API key. | | `breyta service-accounts keys revoke ` | Revoke one service-account API key without deleting the service account. | | `breyta jobs batches create --type --job '{...}' [--job '{...}']` | Create a batch of queued jobs of one type. | | `breyta jobs batches show ` | Show one batch, including aggregate counts and any included jobs. | | `breyta jobs claim --type --worker-id [--lease-duration 2m]` | Claim one job lease for a worker capable of that job type. | | `breyta jobs progress --lease-token ` | Report active job progress while a lease is held. | | `breyta jobs complete --lease-token ` | Mark a leased job terminal with structured outputs, metrics, and artifacts. | | `breyta jobs fail --lease-token ` | Mark a leased job failed with structured error details and artifacts. | | `breyta jobs worker run --type --handler ./handler` | Run a polling worker loop for one job type. | | `breyta jobs worker state [--job-dir /tmp/breyta-job-...]` | Show local worker state for the active job or a kept job directory. | | `breyta jobs worker progress --status running --message "..."` | Report progress for the active worker job. | | `breyta jobs worker attach-file --file ./report.md --label review-report` | Attach a file resource to the active worker job. | | `breyta jobs worker attach-kv --label review-summary --field finding-count=1` | Attach structured key-value data to the active worker job. | | `breyta jobs worker attach-table --label findings --rows-file ./findings.json` | Attach row-shaped output as a table resource for the active worker job. | | `breyta jobs worker finish --summary "..." --output key=value` | Write local success or no-op worker result state. | | `breyta jobs worker fail --message "..." --code my_error` | Write local failed worker result state. | | `breyta incidents list [--status open\|acknowledged\|recovered\|suppressed-until-recovered] [--mine]` | List workspace flow-health incidents, or limit to flows you created with `--mine`. | | `breyta incidents show [--failure-limit ]` | Show one incident with first-class failure rows. | | `breyta incidents lanes [--limit ]` | Show affected lanes or keys for one incident. | | `breyta incidents acknowledge ` | Mark an incident as seen without claiming it recovered. | | `breyta incidents snooze --for 30m\|2h\|1d` | Temporarily suppress incident surfacing for a bounded window. | | `breyta incidents suppress ` | Suppress an incident until the runtime proves it recovered. | | `breyta digests list [--kind immediate\|scheduled] [--status materialized] [--cadence daily\|weekly\|monthly]` | List digest/update artifacts and optionally filter scheduled digests by cadence. | | `breyta digests show ` | Show one digest summary and its incident rollup. | | `breyta digests deliveries [--channel in-app\|email]` | Show per-recipient in-app/email delivery state for a digest. | | `breyta digests mark-read ` | Mark an in-app digest update as read for the current user. | | `breyta digests cadence` | Show the current scheduled digest cadence and settings deep link for the workspace/user. | | `breyta digests cadence set daily\|weekly\|monthly` | Update the scheduled digest cadence for the current workspace/user. | | `breyta resources list [--query ""] [--types file,result]` | List persisted resources with picker-style filters. | | `breyta resources search "" [--limit 10] [--type result|file]` | Search workspace files and persisted blobs. Defaults are compact. | | `breyta resources read [--limit 100] [--full]` | Read content or preview a table. Use `--full` for full payloads. | | `breyta resources table query [--page-mode offset|cursor] [--limit 100]` | Query one table. Add select/where/sort/partition flags as needed. | | `breyta resources table get-row (--row-id \| --key order-id=ord-1) [--partition-key ]` | Fetch one row by row id or keyed lookup. | | `breyta resources table aggregate --metrics-json '[...]'` | Run a bounded aggregate on one table resource. Add where/group/order/partition flags as needed. | | `breyta resources table schema [--partition-key \| --partition-keys ]` | Show schema, indexes, family stats, and partition summaries. | | `breyta resources table export [--out orders.csv] [--partition-key \| --partition-keys ]` | Export a table resource as CSV. | | `breyta resources table import --file orders.csv` | Import CSV rows. Use `--write-mode upsert --key-fields ...` for keyed upserts. | | `breyta resources table update-cell (--row-id \| --key order-id=ord-1) --column status (--value closed \| --value-json 'true') [--partition-key ]` | Update one table cell value. | | `breyta resources table update-cell-format ...` | Add or clear one sparse formatting override for a cell. | | `breyta resources table set-column --column customer-name ...` | Create or update one logical column definition. | | `breyta resources table recompute [--where-json '[[\"status\",\"=\",\"open\"]]'] [--limit 1000] [--offset 0] [--partition-key \| --partition-keys ]` | Recompute materialized values for existing rows. | | `breyta resources table materialize-join --left-json '{...}' --right-json '{...}' --on-json '[...]' --into-json '{...}'` | Materialize a bounded key-based join into a destination table. | | `breyta feedback send --title "..." --description "..."` | Send issue/feature feedback to the Breyta team. | ### Resource Share API Public artifact preview shares are API-only today. Use them when an external recipient should view a generated artifact without logging in. | API route | Purpose | |---|---| | `POST /api/resources/shares` | Create an unlisted no-login preview link for a workspace-owned resource. Body accepts `uri` or `resourceUri`, optional `title`, optional `ttlSeconds`, optional same-origin relative `claimUrl`, and optional `allowDownload`. Requires `X-Breyta-Workspace`. | | `GET /public/artifact-previews/:token` | Open the sanitized read-only public preview page. | | `GET /public/artifact-previews/:token/download` | Download a text-like shared artifact when the share was created with `allowDownload: true`, such as EDN, Markdown, CSV, JSON, XML, JavaScript, form data, or plain text. Uses the same unlisted, revocable, expiring token. | | `DELETE /api/resources/shares/:token` | Revoke a preview link. Requires `X-Breyta-Workspace`. | Use `breyta resources url ` only for temporary signed direct resource access. It is not the outreach preview URL. ## Command Notes - Jobs workers, helper commands, and worker output contracts: [Jobs Control Plane](/docs/reference-flow-jobs) - Service-account creation, API keys, and service-account scopes: [Service Accounts](REFERENCE_SERVICE_ACCOUNTS.md) - Grouping metadata, display-icon selection, publish description, and public discover: [Flow Metadata And Discover](/docs/reference-flow-metadata) - Paid public flow publishing and Stripe seller onboarding: [Paid Public Flows](/docs/guide-paid-public-flows) - Incident, digest, and cadence behavior: [Flow Health](REFERENCE_FLOW_HEALTH.md) - Storage filters, table-resource behavior, partitioning, and table-family limits: [Table Resources](REFERENCE_TABLE_RESOURCES.md) - Delete guards for connections and secrets are binding-based (`draft` / `live` / `installation`). Delete stays blocked until old bindings are moved or unset. ## Advanced Rollout Commands | Command | Use | |---|---| | `breyta flows release ` | Create immutable release. Use `--release-note-file`; configure live bindings first for required slots. | | `breyta flows promote ` | Promote a release to live installation scope. | | `breyta flows versions update --version ` | Update mutable version metadata such as the markdown `releaseNote`. | ## Advanced Install Commands | Command | Use | |---|---| | `breyta flows installations list ` | List installations for a flow. | | `breyta flows installations create --name "..."` | Create an installation. | | `breyta flows installations create --source-workspace-id --local-private-test` | Local dev only: test a private cross-workspace install without public visibility. | | `breyta flows installations get ` | Inspect installation state (`installedVersion`, `latestAvailable`, `updateAvailable`, `policy`). | | `breyta flows configure --schedules '{...}'` | Set author-owned schedule defaults for setup schedules. | | `breyta flows configure --schedule-enable ` / `--schedule-disable ` | Toggle one author-owned schedule override. | | `breyta flows configure --schedule-reset ` | Remove one author-owned schedule override and inherit the flow default. | | `breyta flows installations configure --input '{...}'` | Set installation inputs/config. | | `breyta flows installations configure --schedules '{...}'` | Set installer-owned schedule settings for setup schedules. | | `breyta flows installations configure --schedule-enable ` / `--schedule-disable ` | Toggle one installer-owned schedule. | | `breyta flows installations configure --schedule-reset ` | Remove one installer-owned schedule override and inherit the flow/default setup schedule. | | `breyta flows installations enable ` / `disable` | Enable or pause the installation. Disabled installations reject manual runs, interface calls, and schedules while preserving per-schedule and per-interface settings. | | `breyta flows installations interfaces ` | Show manual/HTTP/webhook/MCP interface surfaces available on the installed flow version. | | `breyta flows installations upload --file ./a.pdf` | Upload files to an installation webhook interface. | | `breyta flows installations delete ` | Delete installation. | New end-user installations default to `track-latest`. `breyta flows release ` always promotes the live target and also promotes track-latest installations by default. Use `--skip-promote-installations` when you only want the live promotion step. Recommended release-note flow: - inspect changes first: `breyta flows diff ` - release with markdown note: `breyta flows release --release-note-file ./release-note.md` - edit later if needed: `breyta flows versions update --version --release-note-file ./release-note.md` ## Functional Split: Draft Vs Live | Aspect | `draft` | `live` | |---|---|---| | Definition source | Latest pushed working copy | Installed released version | | What updates it | `breyta flows push --file ...` | `breyta flows release ` / `breyta flows promote ` | | Default run target | Yes (`breyta flows run `) | No (must use `--target live` or `--installation-id`) | | Primary intent | Fast authoring iteration | Stable installed runtime behavior | ## Run Flag Matrix `breyta flows run ` defaults to draft-target bindings/config resolution. If the flow has never been released and you hit `no_active_version`, run the mutable draft explicitly with `--target draft` during authoring, or release the flow first. | Need | Flags | |---|---| | Default draft target bindings/config | none | | Brand-new unreleased draft | `--target draft` | | Installed live target (advanced) | `--target live` | | Exact installation (advanced) | `--installation-id ` | | Explicit release version | `--version ` | | Runtime input payload | `--input '{...}'` | | Wait for completion | `--wait` (plus optional `--timeout`, `--poll`) | `--target` selects which bindings/config target to use. It does not switch execution to the mutable draft definition. For new flows, declare per-run fields under `:invocations` and expose human launch with `:interfaces :manual`; pass values with `--input`. Older manual field shapes remain executable for compatibility, but new source and new docs should use `:invocations` plus `:interfaces :manual`. `breyta flows release ` updates live installation for the selected workspace by default, but does not change the default run target. Use `--target live` or `--installation-id` when you need installation runtime behavior. `breyta runs list` supports the same structured filter tokens as the web runs list: - `status:` - `flow:` - `installation:` - `version:` ## Docs Search Use [Docs Search](REFERENCE_DOCS_SEARCH.md) for the full query model. Quick examples: | Query pattern | Command | |---|---| | Step config overview | `breyta docs fields http` | | Selected step fields | `breyta docs fields http response-as persist retry --format json` | | Operation-specific step fields | `breyta docs fields files source paths --section read` | | Plain terms | `breyta docs find "flows push"` | | Source filter | `breyta docs find "source:cli release"` | | Phrase + boolean | `breyta docs find "\"end-user\" AND source:flows-api"` | | Pagination | `breyta docs find "install" --limit 20 --offset 20` | | Snippets/explain | `breyta docs find "release" --explain --format json` | ## Compatibility Surface Legacy command aliases remain executable for compatibility but are not part of the canonical command surface. New callable ingress should use `:interfaces` and `breyta flows installations interfaces `. ## Related - [Start Here](/docs/start-here) - [Author Flows](/docs/playbook-author-flows) - [Debug And Verify](/docs/playbook-debug-and-verify) - [Release And Install](/docs/playbook-release-and-install) - [CLI Workflow](/docs/guide-cli-workflow) - [CLI Essentials](/docs/cli-essentials) - [Reference Index](/docs/reference-index) - [Docs Search](REFERENCE_DOCS_SEARCH.md) - [Flow Metadata And Discover](/docs/reference-flow-metadata) - [Flow Health](REFERENCE_FLOW_HEALTH.md) - [Flow Definition](/docs/reference-flow-definition) - [Jobs Control Plane](/docs/reference-flow-jobs) - [Service Accounts](REFERENCE_SERVICE_ACCOUNTS.md) - [Table Resources](REFERENCE_TABLE_RESOURCES.md) - [Flow Configuration](/docs/guide-flow-configuration) - [Connections First](/docs/guide-connections-first) - [Installations](/docs/guide-installations) - [Paid Public Flows](/docs/guide-paid-public-flows) - [Profiles](/docs/guide-profiles) - [Runs And Outputs](/docs/operate-runs-and-outputs) - [Persisted Results And Resource Refs](/docs/guide-persisted-results-and-resources) - [Step Table](/docs/reference-step-table) - [Flow Grouping](/docs/guide-flow-grouping) - [Troubleshooting](/docs/guide-troubleshooting) - [Feedback And Reporting](/docs/guide-feedback-and-reporting) - [CLI Skills](/docs/cli-skills) --- Document: Flow Metadata URL: https://flows.breyta.ai/docs/reference-flow-metadata.md HTML: https://flows.breyta.ai/docs/reference-flow-metadata Last updated: 2026-05-21T15:31:38+02:00 # Flow Metadata And Discover Reference for mutable flow metadata managed around a workspace flow record. ## Quick Answer Use this page when you are managing grouped flows, display-icon selection, discover card media, install-surface copy, marketplace visibility, paid monetization, or public discover visibility. These settings live on the workspace flow record. Some can also be authored in the flow source and persisted on push. ## Metadata Vs Source - `breyta flows show`, `breyta flows pull`, and `breyta flows validate` target draft by default. - Grouping metadata is mutable workspace metadata, not part of the pulled flow source file. - Marketplace visibility is stored metadata. It can also be authored in the flow file with `:marketplace {:visible true}` and then persisted on push. - Paid app monetization is stored metadata. For new source-authored paid apps, put the catalog under `:marketplace {:app {... :monetization {:plans [...]}}}` and persist it with `breyta flows push`. - Legacy flow-level monetization under `:marketplace {:monetization {...}}` remains supported for existing listings, but new paid apps should use the app-owned shape so Discover, checkout, billing, and install ownership share the same app and plan identity. - Display-icon selection is mutable workspace metadata, not part of the pulled flow source file. - Publish description is mutable workspace metadata, not part of the pulled flow source file. - Discover card media is stored metadata. It can also be authored in the flow file with `:publish-media` and then persisted on push. - Public discover visibility is stored metadata. It can also be authored in the flow file with `:discover {:public true}` and then persisted on push. ## Grouping Metadata Use `breyta flows update` to manage grouped-flow metadata: ```bash breyta flows update billing-sync \ --group-key billing \ --group-name "Billing" \ --group-description "Billing operations" \ --group-order 20 ``` Inspect stored grouping state with: ```bash breyta flows list --pretty breyta flows show billing-sync --pretty ``` Grouped-flow responses expose fields such as: - `groupKey` - `groupName` - `groupDescription` - `groupOrder` - `groupFlows` on `flows show --pretty` Clear only ordering: ```bash breyta flows update billing-sync --group-order "" ``` Clear the full group: ```bash breyta flows update billing-sync --group-key "" ``` ## Display Icon Selection Use `--primary-display-connection-slot` to choose which connection/provider icon renders anywhere the flow icon is shown: ```bash breyta flows update billing-sync \ --primary-display-connection-slot crm ``` - Selectors can come from declared `:requires` slots or normalized keys from `:connections`. - JSON responses may include `_hints` that help inspect candidate selectors. - If unset or no longer matched, rendering falls back to the first explicit `:connections` icon, then the first inferred `:requires` icon. Clear the selector with: ```bash breyta flows update billing-sync --primary-display-connection-slot "" ``` ## Marketplace Visibility Use marketplace visibility when the flow should be shown as a marketplace item in discover/install surfaces: ```bash breyta flows marketplace update billing-sync --visible=true ``` Notes: - This is separate from `discover.public`. - The public marketing app page for a visible marketplace flow is `https://breyta.ai/apps/`. - You can also author the same value in source as `:marketplace {:visible true}` and persist it with `breyta flows push`. - Use `breyta flows show billing-sync --pretty` to confirm stored metadata includes `marketplace.visible`. ## Publish Description Use publish description when install/discover dialog copy should differ from the normal flow description: ```bash breyta flows update billing-sync \ --publish-description-file ./publish-description.md ``` Or inline: ```bash breyta flows update billing-sync \ --publish-description "## Install this flow" ``` - This markdown is used on install/discover dialog surfaces for installable flows. - If no publish description is set, install dialogs fall back to the normal flow description. - Discover cards still use the regular short description preview. Clear only the publish description: ```bash breyta flows update billing-sync --publish-description "" ``` ## Discover Card Media Use discover card media when public discover/install cards should show a curated image or video preview instead of only the fallback hero: ```bash breyta flows update billing-sync \ --publish-media-type image \ --publish-media-source-kind https-url \ --publish-media-source https://cdn.example.com/hero.png \ --publish-media-alt "Billing dashboard preview" ``` Video media can also include an optional poster: ```bash breyta flows update billing-sync \ --publish-media-type video \ --publish-media-source-kind https-url \ --publish-media-source https://cdn.example.com/demo.mp4 \ --publish-media-poster-kind https-url \ --publish-media-poster https://cdn.example.com/demo-poster.jpg \ --publish-media-alt "Billing reconciliation walkthrough" ``` Behavior: - `publishMedia` replaces the whole discover card media value each time you set it - Use `--clear-publish-media` to remove it explicitly - `https-url` sources require `https://...` - `flow-resource` sources require a same-workspace `res://...` URI. Use only reviewed resources intended for public Discover/install card media. - Poster media is optional and only supported for video - If you keep the flow in source control, you can author the same value as `:publish-media` in the `.clj` flow file and push it Clear discover card media: ```bash breyta flows update billing-sync --clear-publish-media ``` ## Public Discover Visibility Use these commands when the flow should appear in discover/install surfaces: ```bash breyta flows discover list breyta flows discover search billing breyta flows discover update billing-sync --public=true ``` Important distinctions: - `breyta flows discover list` and `discover search` browse the public installable catalog for the current workspace. - `breyta flows search` is separate. It searches actual workspace flow metadata, not the public discover/install catalog. Approved reusable templates live under `breyta flows templates search`. - Public discover requires explicit public visibility and a released installable flow. - Public app page proof uses the marketing route `https://breyta.ai/apps/`. Keep it distinct from workspace install URLs such as `//flows//installations`. - Public listings can include manual interfaces, schedules, and webhook interfaces. Webhook interface setup is managed per installation. Legacy triggers remain visible for existing flows. ## Paid Monetization For normal creator work, use the flow Publish page: ```text //flows//publish ``` The Publish page saves public listing, paid pricing, optional trial, and live publish state together. Paid checkout from the app path is before install for one-time and subscription flows. Usage pricing is stored as before-run monetization metadata while run metering and Stripe usage invoicing are rolled out. Source-first authoring remains useful for code-reviewed flow definitions, fixtures, and CLI-heavy operations. New paid apps should use an app-owned catalog in source: ```clojure {:slug :customer-insights :name "Customer Insights" :tags ["analytics" "customer-insights"] :discover {:public true} :marketplace {:visible true :app {:app-id "customer-insights-suite" :app-name "Customer Insights" :app-primary-flow-slug "customer-insights" :app-flow-slugs ["customer-insights"] :monetization {:plans [{:plan-id "starter" :name "Starter" :default true :pricing {:type "subscription" :amount 29 :currency "usd" :interval "month"} :trial {:days 7 :payment-method-required false}} {:plan-id "pro-pack" :name "Pro Pack" :pricing {:type "usage" :amount 49 :currency "usd" :unit "run" :included-quantity 250}}]}}}} ``` Pricing model summary: - `:free`: no paid gate - `:one-time`: one upfront purchase for paid access - `:subscription`: recurring access subscription - `:usage`: prepaid run pack paid once for a fixed number of successful runs - `:subscription-usage`: recurring subscription with a fixed run allowance each billing period Run-counted behavior: - successful completed runs consume allowance - failed, cancelled, terminated, and timed-out runs do not consume allowance - `:usage` allowance does not expire by time - `:subscription-usage` allowance resets on renewal - `:subscription-usage` overages are not billed yet; exhaustion blocks further runs until renewal Supported values: - `pricing.type`: `:free`, `:one-time`, `:subscription`, `:usage`, `:subscription-usage` - `pricing.amount`: positive price in major currency units. Decimals are allowed when the currency supports them, for example `"0.99"` USD. - `pricing.currency`: lowercase ISO code such as `"usd"` - `pricing.interval`: `:month` or `:year` for subscriptions and subscription usage - `pricing.unit`: `:run` for usage and subscription usage - `pricing.free-quantity`: zero or more free trial runs for `:usage`, or fallback no-card trial runs for `:subscription-usage` when `trial.runs` is not set - `pricing.included-quantity`: positive paid run quantity for `:usage`, or positive included runs per billing period for `:subscription-usage` - `paywall.when`: `:before-install` or `:before-run` for paid flows - `trial.days`: positive integer, optional - `trial.payment-method-required`: `true` or `false`, optional - `plans`: app-owned catalog entries under `:marketplace {:app {:monetization {:plans [...]}}}`. Every plan must normalize to a unique stable `plan-id`, every entry must be valid, and at most one plan can be marked `default` or `recommended`. If no default is set, the first plan is treated as the default. - Seat-based pricing is not implemented. Do not describe a plan as "N seats" or "N installs" unless the product has added explicit seat entitlements. Use run quantities only for usage-priced plans. Trial compatibility: - `:subscription` uses `trial.days` - `:one-time` and `:usage` use `trial.runs` - `:subscription-usage` can use explicit `trial.runs` - if `:subscription-usage` omits `trial.runs`, `pricing.free-quantity` acts as the fallback no-card trial-run allowance - if both are present on `:subscription-usage`, explicit `trial.runs` wins Legacy flow-level examples, supported for existing listings: ```clojure {:tags ["billing" "automation"] :marketplace {:visible true :monetization {:pricing {:type :subscription :amount 25 :currency "usd" :interval :month} :paywall {:when :before-install} :trial {:days 7 :payment-method-required true}}} :discover {:public true} ...} {:tags ["usage" "automation"] :marketplace {:visible true :monetization {:pricing {:type :usage :amount 100 :currency "usd" :unit :run :included-quantity 500} :paywall {:when :before-run} :trial {:runs 5 :payment-method-required false}}} :discover {:public true} ...} {:tags ["catalog"] :marketplace {:visible true :monetization {:pricing {:type :subscription-usage :amount 49 :currency "usd" :interval :month :unit :run :included-quantity 100} :paywall {:when :before-run}}} :discover {:public true} ...} ``` If you operate directly against `/api/commands`, the underlying monetization command is `flows.marketplace.monetization.update`, but source-authored paid apps should prefer the app-owned catalog shape above. CLI metadata update commands set visibility and other metadata; they do not replace the source plan-catalog authoring path. For day-to-day paid-flow publishing, prefer the Publish page so saved metadata and the live buyer version do not drift. For seller onboarding and payout prerequisites, see [Paid Public Flows](/docs/guide-paid-public-flows). ## Public Flow Checklist 1. If the flow is a new paid app, add `:marketplace {:app {... :monetization {:plans [...]}}}` in source. Keep legacy `:marketplace {:monetization {...}}` only when preserving an existing legacy listing. 2. Make every connection requirement ownership explicit, including `:type :secret` requirements: - Use `:provided-by :author` for author-owned credentials the public flow should run with, such as Apify or OpenAI. - Use `:provided-by :installer` for user-owned accounts each installer must connect themselves. - Public release checks reject connection slots that omit `:provided-by`. 3. Configure and check author-owned live bindings before release: - `breyta flows configure --target live --version latest --set .conn=` - `breyta flows configure check --target live --version latest` 4. Add install-surface copy and discover card media when the public surface should be polished: - Install dialog markdown: `breyta flows update --publish-description-file ./publish-description.md` - Discover card hero: `breyta flows update --publish-media-type image --publish-media-source-kind https-url --publish-media-source https://...` 5. Choose one marketplace visibility path: - Source-first: add `:marketplace {:visible true}` to the flow definition before push only after explicit approval. - Explicit after push: run `breyta flows marketplace update --visible=true` only after explicit approval. 6. Choose one discover visibility path: - Source-first: add `:discover {:public true}` to the flow definition before push only after explicit approval. - Explicit after push: run `breyta flows discover update --public=true` after the flow has been pushed and only after explicit approval. 7. Push the flow. 8. If you chose an explicit marketplace/discover path, run those updates after push. 9. Release or promote so there is an installable live version. 10. Verify it in the web Discover UI, or with `breyta flows discover list` / `breyta flows discover search `. 11. Verify the marketing app page at `https://breyta.ai/apps/`. ## Ordering Notes - `groupOrder` is explicit manual metadata. Lower numbers sort first. - Use spaced values such as `10`, `20`, `30` so future insertions are easy. - `flows show --pretty` returns sibling `groupFlows` ordered by explicit `groupOrder` when present. ## Live Binding Promotion Use this command when live bindings should start from the draft target: ```bash breyta flows configure billing-sync \ --target live \ --from-draft \ --version latest ``` ## Related - [CLI Commands](/docs/reference-cli-commands) - [Paid Public Flows](/docs/guide-paid-public-flows) - [Installations](/docs/guide-installations) - [Flow Grouping](/docs/guide-flow-grouping) - [Flow Definition](/docs/reference-flow-definition) --- Document: Glossary URL: https://flows.breyta.ai/docs/glossary.md HTML: https://flows.breyta.ai/docs/glossary Last updated: 2026-05-10T21:06:10+02:00 # Glossary Breyta glossary for canonical lifecycle terminology. ## Flow Workflow definition that can be edited, released, installed, and run. ## Working Copy Editable authoring state used by `pull/push/validate`. ## Release Immutable published version created from working copy. ## Installation Runtime target for a flow with its own config (inputs/bindings), enabled state, and selected version. ## Live Scope Default workspace runtime target for production-like runs. ## End-User Installation Per-user installation target for end-user flows. ## Run Single execution instance of a flow against a resolved installation target. ## Requires Declared runtime dependencies a flow needs (connections, secrets, form/input values). ## Bindings / Configuration Concrete values mapped to `:requires` and installation inputs. ## Promote Action that points an installation scope/target to a release version. ## Trigger Legacy start mechanism for older flow source. New manual/webhook/client ingress is authored under `:interfaces`, and new time-based automation is authored under `:schedules`. ## Template Reusable text block rendered with runtime data. ## Function Ref `:function` step reference to reusable Clojure code in top-level `:functions`. ## Wait Step that pauses execution until signal/action/timeout. ## Persisted Result Step output stored as a reference instead of large inline payload. ## Internal: Profile / Profile ID Internal runtime representation used by backend/API internals. Not the canonical end-user lifecycle term. ## CLI Config Profile (`--profile`) CLI client context selector (API URL/token/workspace). This is distinct from internal runtime profiles. ## Related - [Core Concepts](/docs/reference-core-concepts) - [CLI Commands](/docs/reference-cli-commands) - [CLI Workflow](/docs/guide-cli-workflow) --- Document: Breyta CLI Skill URL: https://flows.breyta.ai/docs/breyta-cli-skill.md HTML: https://flows.breyta.ai/docs/breyta-cli-skill Last updated: 2026-05-21T12:24:31+02:00 --- name: breyta description: >- Use Breyta CLI for Breyta flows: create, edit, debug, run, validate, release, install, publish public/Discover/marketplace flows, search approved templates, inspect docs/bindings/tables/fanout/outputs/provider APIs/models, and report full Breyta URLs. --- ## Purpose Use this skill for meaningful Breyta flow work. Keep this file as the router: pick the task mode, load the smallest playbook/reference, run bounded commands, and report proof with exact Breyta URLs. ## Flow DSL Mental Model A Breyta flow source file is orchestration DSL, not a general Clojure app. Declare contracts early, compose bounded `flow/step` calls, inline small first-proof transforms, and extract repeated or bulky pieces before release. Keep side effects at step boundaries and persist large data as resource refs. If the task is to import or migrate an n8n workflow JSON, use `breyta flows import n8n ` first. Do not hand-write the initial EDN conversion unless the CLI importer is unavailable or the user explicitly asks for a manual conversion. ## Start Of Session 1. If the CLI warns that this installed skill is stale, follow the shown `breyta skills install ...` or `breyta skills status ...` command before material flow edits. If CLI behavior and this guidance disagree, trust `breyta help ` plus a narrow docs search. 2. Keep a tiny session capsule: task mode, workspace/flow, target/interface, refs consulted, chosen pattern, next proof command, known risks. 3. Use `breyta help ` only when command shape is uncertain. Use `breyta docs fields [field...]` for step config rows and `breyta docs find "" --limit 5 --format json` for broader primitives. ## Task Mode Router Pick one primary mode before discovery. Escalate only when evidence is incomplete. | Mode | First evidence | Load | | --- | --- | --- | | Create/edit flow | current flow or nearby pattern, touched primitive docs | `playbooks/author-flows.md` | | Debug/verify run | failed run, error URL/action, resource/output ref | `playbooks/debug-and-verify.md` | | Release/install | diff, target, bindings, live/install proof need | `playbooks/release-and-install.md` | | Public/marketplace | visibility, pricing, copy, Discover, install surface | `playbooks/public-and-marketplace.md` | | Reliability | fanout, paging, child flows, concurrency, checkpoints | `playbooks/advanced-reliability.md` | | Output/table/media | artifact/resource/table proof | `references/outputs-and-tables.md` | | Provider/API/model | endpoint, auth, rate limit, request body, model id | `references/provider-api-freshness.md` | | n8n import | workflow JSON path/export, generated importer TODOs | `references/n8n-import.md` | ## Playbook Matrix Read one playbook by default: - `playbooks/author-flows.md`: templates, existing data, interfaces, lint/push, single-step development, resource refs, installable source shape. - `playbooks/debug-and-verify.md`: failed runs, UI mismatch, readback, side effects, feedback reports. - `playbooks/release-and-install.md`: draft/live, release/promote, live target configuration, install-shaped proof. - `playbooks/public-and-marketplace.md`: Discover, marketplace, public copy, paid/public surfaces, author approval. - `playbooks/advanced-reliability.md`: fanout, paging loops, concurrency, checkpoints, large artifacts. For complex flows, start with authoring and phase-load only the surface touched next: provider/API, advanced reliability, outputs/tables, then release/public. Load references only for exact field/step/schema truth: - `references/runtime-data-shapes.md` - `references/outputs-and-tables.md` - `references/provider-api-freshness.md` - `references/reliability-and-concurrency.md` ## Default Command Budget - Start with compact search/list defaults and `--limit 5`. - Treat search like `rg`: keep hit refs, matched fields/surfaces, use grep `--surface` when noisy, and open one focused target at a time. - Cache inspected flows, docs hits, template hits, refs, and workflow ids. - Do not repeat identical search/help/docs commands unless state or the question changed. - Use `docs fields` for step keys; otherwise one docs search before full docs. - Inspect at most one full template for normal create/edit work. - Read each resource URI once unless it changed. - After two failed edit/run cycles, stop and re-plan. ## Authoring Defaults - Build small draft slices: contract, one manual interface, one meaningful boundary, lint, push, configure check, run, read output. - New callable flows should use `:interfaces` and `:invocations`. Declare at most one manual interface; use invocation inputs for manual mode choices. Use `--interface-id ` only when selecting the declared manual interface explicitly. Treat `--trigger-id` as legacy compatibility. - Run `breyta flows lint --file ./flows/.clj` before push when editing a local source file. Use `--local-only` for fast offline checks or `--server` when canonical pre-push checks are required. - `breyta flows validate ` checks stored draft/live state after push; it is not a substitute for runtime proof. - Use `breyta flows readiness ` before release passes to see definition, configuration, public/discover/marketplace, pricing, installability, blockers, and next commands in one compact report. - Use `breyta steps run --flow --source draft ...` to isolate one draft-context primitive before rerunning the whole flow. Use inline `--params` for small maps, `--params-file` for larger inputs, compact `resultPreview` first, `--result-path` / preview limits for focused expansion, `--result-file` for full local capture, and `--full` only when necessary. - Persist unknown/large payloads with `:persist`; pass refs and hydrate with `:load` only where needed. Use `:tier :ephemeral` for temporary HTTP blobs. - Keep function steps map-oriented. Prefer map access, `json/parse`, `json/write-str`, and `breyta.sandbox/*` helpers. - For third-party APIs/databases/OAuth/LLMs, check connections before building. If none fits, hand off a Breyta setup/connection URL. - Never ask users to paste secrets in chat. ## Lifecycle And Approval Boundaries - Draft is staging/current workspace authoring. Live is released/runtime. - Say `draft verified` when only draft was exercised. - Use `breyta flows release-check --public --marketplace` for public paid release gates before changing visibility or telling a user the flow is ready. - Do not call public/end-user work ready from draft proof alone; verify live/install-shaped behavior or state `web UI not verified`. - Ask for concrete missing config; do not invent connections, secrets, installation inputs, private URLs, workspace ids, or user ids. - Public visibility, marketplace visibility, paid-flow settings, release, and promotion require explicit author approval. - For new source-authored paid apps, use `:marketplace {:app {... :monetization {:plans [...]}}}`. Preserve legacy `:marketplace {:monetization ...}` only for existing listings. ## Proof Contract Final reports should include: 1. Problem contract: interface, inputs, outputs, integrations, failure behavior. 2. Discovery proof: docs searched, template/workspace/resource searches, selected and rejected patterns. 3. Flow delta: slug, files, steps, bindings, config, lifecycle target. 4. Runtime proof: commands, workflow ids, target/version/install path, output or resource readback, side effects, full Breyta URLs. 5. Risk ledger: unverified surfaces, especially UI/install/public output gaps. Include significant authoring friction: excessive trial/error, misleading docs/help, unclear CLI/API behavior, or missing examples. ## Output Guidance - Public/end-user flow outputs should default to one readable Markdown artifact, or a deliberate table/media viewer when that is better for the user. - The default rich output pattern is a Markdown report. If the report needs real Breyta resources, use fenced `breyta-resource` blocks to embed table snapshots, charts, downloads, images, video, nested Markdown, text, or JSON in document order. - Keep raw debug maps, internal ids, `res://` refs, and implementation-only fields out of final public output unless the user explicitly asked for them. - Verify table resources with `breyta resources read `; workflow resource lists alone are not enough. - For large artifacts, report resource refs, signed URLs, and short previews instead of pasting full table/resource content. - Persist long Markdown reports or JSON bodies as blobs/resources and store refs plus short summaries in tables. ## Definition Of Done 1. Working copy validates for intended flow changes. 2. At least one representative `flows run` succeeds for the default target. 3. At least one representative run reaches expected terminal status. 4. Required side effects/output are confirmed, not inferred. 5. Public/end-user output is a presentation surface, not a raw debug payload. 6. Returned artifacts are inspected and reviewed from the perspective of the intended user/audience. 7. Image artifacts are reviewed with vision analysis; video artifacts are sampled with screenshots and reviewed with vision analysis when tooling is available. 8. Table resources, when used, are read back with `breyta resources read `. 9. For public/end-user flows, live/install-shaped behavior is verified or the risk ledger says `web UI not verified` / install path not verified. 10. Report includes docs searched, template queries, chosen/rejected templates, target/version proof, evidence, full Breyta URLs, and unresolved risks. ## Failure Triage 1. Command uncertainty: `breyta help `. 2. Primitive uncertainty: `breyta docs find "" --limit 5 --format json`. 3. Config mismatch: auth, workspace, target, version, installation, required slots. 4. Runtime mismatch: start with `breyta runs events --limit 100`; add `--step ` for one step or `--installation-id ` for an installed-profile run. Inspect one step with `breyta runs step ` or `breyta runs inspect --step `, using `--full` only when captured output/error payloads are required. Isolate the primitive, then rerun the intended interface. For waits, approve deliberately with `breyta runs continue --approve-latest-wait`. 5. Authoring friction or platform gap: `breyta feedback send` with full URLs, workflow ids, target, interface id, output/resource refs, commands tried, and why the loop was confusing or wasteful. --- Document: Breyta CLI Skill: Authoring Loop URL: https://flows.breyta.ai/docs/breyta-cli-skill-authoring-loop.md HTML: https://flows.breyta.ai/docs/breyta-cli-skill-authoring-loop Last updated: 2026-05-21T12:24:31+02:00 # Breyta Authoring Loop Reference Load this reference before creating or editing a Breyta flow, changing bindings, running release/install operations, or making non-trivial CLI decisions. ## Progressive Flow Development Playbook Develop in small, proven slices. Do not try to solve the whole workflow in one large flow definition. 1. **Reuse first:** run the bounded search ladder, choose one nearby pattern, and record what you will reuse or ignore. 2. **Contract first:** define distribution, `:requires`, `:invocations`, `:interfaces`, output shape, side effects, and `:concurrency` before writing orchestration. 3. **Small skeleton:** push a minimal flow with one manual interface, one safe input, a small output, and no external side effects. Run it once. 4. **One boundary at a time:** add one connection, provider call, table, job, agent, or child flow at a time; push, configure-check, run, and inspect output before adding the next boundary. 5. **Persist early:** if a response, file, export, transcript, report, page set, or generated media may be large or reused, add `:persist` at the producing step and pass refs, not large inline bodies. 6. **Escalate references only when touched:** load outputs/tables docs for `:persist`, resource refs, viewers, or tables; reliability docs for paging, fanout, waits, retries, or checkpoints; provider docs for external API shape. 7. **Installable mindset:** keep author-only workspace ids, emails, secrets, resource ids, and private URLs out of source; expose setup/run choices through `:requires`, `:invocations`, and `:interfaces`. 8. **Proof loop:** verify the same interface/input/target a user will exercise, then inspect the rendered output or resource preview before calling it done. ## Draft And Live Lifecycle Use precise lifecycle language: - `draft` is the staging/current workspace authoring state. Use it for source edits, configure checks, low-risk smoke tests, and iteration. - `live` is the released/runtime state. A flow is unreleased until a version is released or activated and the live path is verified. - End-user installs, public Discover behavior, scheduled/webhook/MCP operation, and long-lived production use are live-shaped concerns. - Do not call a flow released, complete, public-ready, or end-user ready from draft evidence alone. Say "draft verified" when only draft was exercised. - Before release, inspect draft-vs-live with `breyta flows diff `. After release or promotion, smoke-run the live/install-shaped path when side effects are safe. ## Solution Surfaces Use reusable definition surfaces deliberately, then compose them in `:flow`. Inline small first-proof transforms; extract repeated, bulky, public-facing, or tool-exposed logic before release. Assume each new or edited flow may later become public unless the user explicitly says it is internal-only. Public-ready authoring is an architecture constraint, not approval to publish. Default authoring order: 1. `:requires` - declare connections, secrets, installer inputs, form inputs, and worker dependencies first - model environment-specific values as author/installer/run inputs instead of hardcoding workspace ids, user ids, emails, test resource ids, private URLs, credentials, or one-off local assumptions - in hosted UI chat, when the workspace is explicitly reported as fresh/empty, skip broad flow/connection inventory - start with targeted connection inventory: - `breyta connections list` - `breyta connections show ` for the connection you plan to reuse - `breyta connections test ` only when you plan to bind or debug that connection - reuse existing workspace connections before inventing new setup - if no healthy third-party connection exists, hand off the Breyta setup/edit URL and do not ask for secrets in chat - protected setup/edit links should return through login - choose stable slot names around business capability (`:github-api`, `:crm`, `:llm`, `:slack`) rather than transient provider names - if packaged `:steps` or `:agents` depend on a broad connection, narrow them here with flow-local permission hints before exposing them as tools 2. `:templates` - move prompts, request bodies, SQL, notification content, and other large static text here - avoid large inline strings inside `:flow` or heavy step configs - keep public-facing copy in named templates when it is reused by prompts, notifications, output artifacts, or publish descriptions 3. `:functions` - put shaping, normalization, projection, and deterministic transforms here - prefer named reusable transforms over long inline SCI in orchestration 4. `:steps` - package heavy built-in step configs behind a smaller input/output contract - especially for `:http`, `:db`, `:notify`, `:function`, and other broad raw surfaces - prefer publishing packaged `:steps` as tools instead of exposing raw heavy step surfaces to agents 5. `:agents` - define reusable named agent configs here when the behavior is agent-shaped - put objective, instructions, memory, cost/evaluate/trace, and tool/delegation config in the named agent definition - use `:tools {:steps [...] :agents [...]}` to compose agent-to-step and agent-to-agent delegation 6. `:flow` - wire steps together in deterministic orchestration; extract inline logic when reuse or review size justifies it - keep `:flow` focused on sequencing, branching, waits, persistence, and summary For agentic/reviewer/fixer flows, the default pattern should be: - `:files` for code/resource state - packaged `:steps` for heavy or externally scoped operations - named `:agents` for reviewer/fixer/coordinator roles - orchestration last Anti-patterns: - large raw prompts embedded directly in `:agent`, `:llm`, or `:http` steps - repeated one-off normalization logic in `:flow` - giant all-in-one flows when a packaged step, child flow, or staged draft iteration would isolate risk - exposing raw broad built-in step surfaces directly to agents when a packaged step would be safer - treating `:flow` as the place to define reusable behavior - hardcoding author workspace state into a flow that an installer or public user may run later ## Function And Data Shape Discipline Most step outputs and inline returns are ordinary Clojure maps with keyword keys. Do not add broad defensive parsing layers unless the documented output shape is genuinely mixed or external. - Prefer destructuring, `get`, `get-in`, `select-keys`, `update`, `assoc`, and `cond->` over custom parser functions. - Put reusable transforms in top-level `:functions`; call them with `flow/step :function` and a small `:input` map. - Use `json/parse`, `json/write-str`, and `breyta.sandbox/*` helpers inside `:function` code instead of bespoke JSON/base64/hash/date parsing. - If a value is already a parsed map from an HTTP `:accept :json`, table query, resource picker, or Breyta step result, access it directly; do not stringify and parse it again. - Keep function outputs shaped for the next step: maps for structured data, vectors of row maps for tables, and resource refs for large artifacts. ## Task Mode Router Choose one primary mode before running commands. Load the compact skill plus the reference files for the touched surface. Escalate only when the evidence is incomplete. | Task mode | First evidence | Escalate when | |---|---|---| | Existing-flow edit | Current flow summary or pulled source, touched primitive docs, nearest primitive example | The change crosses steps, changes dependencies, or affects public/install/output behavior | | New flow | Nearby workspace flows, docs for interfaces and primitives, approved example metadata | Primitive examples do not explain architecture or setup | | Primitive/step edit | Current step config, primitive docs, matching primitive snippet, referenced dependencies | Snippet dependencies are unclear | | Debug run | Failed run summary, error/action URLs, touched step config, relevant resources | Two failed edit/run cycles do not isolate the cause | | Public publish/install | Public reference, install/setup/run surfaces, release/promote target, URLs | Visibility, marketplace, copy, media, setup, or public output changes | | Output/table | Output reference, target artifact/resource URI, bounded readback | The artifact cannot be inspected from the CLI output alone | | Provider/API | Provider reference, official provider docs/API reference, Breyta primitive docs/example | Current provider docs disagree with an approved example | | Release | Diff, release note, target version/profile/install policy | Behavior proof is missing or public approval is not explicit | ## Scoped Discovery Use docs and command help as scoped tools, not a global preflight. - Skill drift check: treat CLI missing-skill and stale-skill warnings as drift, not noise; install or refresh with the command in the warning before creating or materially editing flows - when guidance, CLI behavior, or returned shapes disagree, run `breyta skills status --provider all`, then verify the command shape with `breyta help ` and a narrow docs search - search docs once per changed primitive before opening docs pages - for agent parsing, prefer `breyta docs find "" --limit 5 --format json`; use the default TSV output only for quick human scanning - use `breyta help ` only when command shape or flags are uncertain - avoid broad help/docs sweeps when the bundled reference already gives the command shape - do not repeat an identical docs/help/search command unless state or the question changed - cache inspected docs hits, example snippets, resource reads, and run ids in session notes Lookup patterns before inferring implementation details: - primitive or surface name: - `breyta docs find "files materialize"` - `breyta docs find "packaged steps"` - exact phrase when you know likely wording: - `breyta docs find "\"draft setup\""` - `breyta docs find "\"tool permissions\""` - command path or operator surface: - `breyta docs find "source:cli connections test"` - `breyta docs find "source:cli flows configure check"` - API/runtime side when CLI docs are thin: - `breyta docs find "source:flows-api form requirements"` - `breyta docs find "source:flows-api agent definitions"` - error text or recovery text: - `breyta docs find "\"Bad credentials\""` - `breyta docs find "\"missing api base url\""` If the first search misses: 1. search the primitive name 2. search the command name 3. search the exact error text 4. search with a source filter 5. only then infer a fallback If command shape is the question, finish with `breyta help ` instead of guessing flags. ## Primitive-First Example Reuse Use `breyta flows search` to inspect actual workspace flow metadata before creating or editing behavior. Use `breyta flows grep` for workspace source and config literals. Use `breyta flows templates search/grep` for approved reusable templates. None of these are the public installable catalog. The goal is to understand the smallest working approved pattern before inventing structure. For step-level edits, do not start by pulling a full template. Start with metadata, primitive snippets when available, and only the dependencies referenced by the matching snippet. Reuse ladder: 1. For new flows, search nearby workspace flows first: `breyta flows search "" --limit 5`. For existing flows, pull or show the current flow first. 2. Search docs for the primitives or surfaces you will touch. 3. Search workspace source/config when metadata is insufficient: `breyta flows grep "" --surface definition,tools --or "" --step-type --tool-name --limit 5`. 4. Search approved templates: `breyta flows templates search "" --limit 5`. 5. Review name, description, tags, providers, tool names, connection slots, step types, step count, compact publish/steps previews, and `flow_web_url`. 6. Extract or inspect the matching primitive snippet when available. 7. Include only referenced dependencies: `:requires`, `:templates`, and `:functions`. 8. Pull or inspect one full template only when architecture-level reuse is needed. 9. For edits, compare only the touched surface against the closest local or approved example before changing structure. 10. If no useful example exists, say that explicitly and continue from docs. Stop discovery once you have current flow state, one relevant local or approved pattern, and docs for the primitives you will touch. ## Existing Data And Template Playbook Before writing a new flow from scratch, make the reuse/data path explicit: 1. Search nearby workspace metadata: `breyta flows search "" --limit 5`. 2. Search workspace source only for exact primitives or strings: `breyta flows grep "" --surface definition,tools --limit 5`. 3. Search approved reusable templates: `breyta flows templates search "" --limit 5`. 4. Search template source when metadata is not enough: `breyta flows templates grep "" --surface steps,tools --limit 5`. 5. Search docs for the chosen surface: `breyta docs find "" --limit 5 --format json`, then open one focused section with `breyta docs show --section ""`. 6. If the flow should reuse existing reports, uploads, or prior run output, search resources with `breyta resources search "" --limit 5` and read only the best match with `breyta resources read --limit 5`. Use full template/source/resource output only when compact metadata cannot answer the design question. Record the selected and rejected workspace/template/data patterns in the final discovery proof. Full template inspection is required only for: - cross-step architecture reuse - public install patterns - multi-flow orchestration - fanout or child-flow behavior - unclear snippet dependencies - copying overall flow structure Final reports must include: - approved example queries run - chosen snippet or template, if any - rejected snippets or templates when they looked close but were not used - which structure was reused or intentionally ignored Do not treat a template name alone as enough context. The agent must understand the step types, setup/run field shape, bindings, output shape, and public copy before copying the pattern. ## Command Budget - Authenticate once per session unless auth/workspace state changes. - Do not repeat an identical command unless state changed. - Use one docs search per changed primitive before opening docs pages. - Use one full template inspection at most for a normal create/edit task. - Read each resource URI once unless the resource changed. - Run one final `breyta flows diff ` before release. - After two failed edit/run cycles, stop and re-plan before pushing another change. - Do not run `breyta connections test --all` by default. Test only the connection you plan to bind or debug. ## Workflow Quality Contract Before editing, write a contract scaled to the task mode: - inputs and outputs - side effects and exactly-once requirements - idempotency or dedupe strategy - failure behavior, retries, and timeouts - observability proof: result fields, counters, run ids, resources, or side-effect evidence - user-facing output review path For primitive/step edits, scope the contract to the changed primitive. For new flows, release work, public installs, and cross-step changes, cover the full flow. ## Manual CLI Smoke Interfaces For new flows that must be smoke-tested from the CLI, declare the run input contract once under `:invocations` and expose the author run button as a manual interface. A brand-new flow also needs an explicit `:concurrency` policy before it will push successfully: ```clojure {:concurrency {:type :keyed :key-field :query :on-new-version :supersede} :invocations {:default {:inputs [{:name :query :type :text :required true}]}} :interfaces {:manual [{:id :run :label "Run" :invocation :default :enabled true}]}} ``` Pass test data with `breyta flows run --input '' --wait`, or call an authored HTTP interface with `breyta flows interfaces call --input '' --wait`. Old manual field shapes are still accepted so existing flows keep running, but do not use that path in new source or new docs. Move run fields to `:invocations` and expose the launch surface with `:interfaces :manual`. For the enabled manual interface, `breyta flows run --input '' --wait` can use the default. Flow source may declare at most one manual interface; model alternate manual paths as invocation inputs such as `mode`. Pass `--interface-id ` only when you need to select that declared interface explicitly. Keep `--trigger-id` for legacy compatibility only. ## Real Payloads Early When integrating webhooks, PR events, provider callbacks, or notification flows, shape the first useful test from a real payload where possible. Use the provider event body, `gh pr view --json ...`, a captured webhook request, or the exact email/notification payload the flow will submit. Synthetic inputs are useful for unit isolation, but they should not be the only proof before release. ## Parent/Child Flow Grouping When a flow calls child flows with `flow/call-flow`, `:fanout` child workflow items, or a similar orchestration pattern, group the related flows so users can see the relationship in Breyta. Grouping is display/workspace metadata. It does not create the runtime parent/child relationship and it does not round-trip through `flows pull` / `flows push`. Runtime linkage still belongs in the parent flow body. Use the same group metadata across related flows: ```bash breyta flows update parent-flow \ --group-key support-agent-suite \ --group-name "Support Agent Suite" \ --group-description "Parent and child flows for support automation" \ --group-order 10 breyta flows update child-flow \ --group-key support-agent-suite \ --group-name "Support Agent Suite" \ --group-order 20 ``` Use spaced `--group-order` values such as `10`, `20`, and `30` so future child flows can be inserted. Verify with: ```bash breyta flows show parent-flow ``` Look for `groupKey`, `groupName`, `groupOrder`, and ordered `groupFlows`. ## Canonical Execution Loop 1. Understand the contract: - interface mode, integrations, expected output, and failure policy 2. Bootstrap from existing artifacts and connections: - inventory the connections the flow needs before editing behavior, except in clearly empty hosted workspaces - test only the connection you plan to bind or debug - decide which business capabilities become `:requires` slots - decide which existing workspace connections should satisfy those slots - if a flow exposes packaged `:steps` or `:agents` as tools, decide whether reused connections need flow-local scope limits - prefer existing flow file first: - `breyta flows pull --out ./tmp/flows/.clj` - before changing structure, follow the discovery order: - current state - docs search - approved example metadata - primitive snippet - referenced dependencies - full template only if needed - compare - edit - draft proof - live/install proof 3. Working copy iteration: - build `:requires` early, prove small slices, then extract repeated or bulky logic into `:templates`, `:functions`, packaged `:steps`, and `:agents` - `breyta flows push --file ./tmp/flows/.clj` - `breyta flows configure ...` when required - `breyta flows configure check ` - if the flow belongs to a grouped sequence of dependent flows, set explicit order with `breyta flows update --group-order ` - re-check grouping after metadata changes with `breyta flows show ` so `groupFlows` and ordering metadata are visible - live target updates after slot changes: use `--target live --version ` and `--from-draft` when promoting draft setup - optional read-only verification: `breyta flows validate ` 4. Run and verify: - canonical run: `breyta flows run --input '' --wait` - installation-targeted run when required: `breyta flows run --installation-id --wait` - run with the same interface/input shape, target, bindings, and side-effect path that production will use - for changed primitives, isolate the primitive first, then run the full flow end-to-end - inspect run: `breyta runs show ` - open or report `webUrl` and `outputWebUrl` when present - inspect persisted artifacts with `breyta resources search "" --limit 5`, `breyta resources list --limit 10`, and `breyta resources workflow list --limit 10` - inspect side effects directly: sent email payloads, created rows, updated records, external API responses, or callback/webhook delivery results - for public/end-user work, do not say "ready for UI" from draft proof alone; verify the live/install-shaped path or state `web UI not verified` in the risk ledger 5. Advanced rollout only when needed: - `breyta flows diff ` - `breyta flows release --release-note-file ./release-note.md` - `breyta flows versions update --version --release-note-file ./release-note.md` - `breyta flows promote ` - `breyta flows installations configure --input '{...}'` ## Install Operations Use these for end-user/runtime setup: - `breyta flows installations list ` - `breyta flows installations get ` - `breyta flows installations interfaces ` - `breyta flows installations upload --file ./a.pdf` for webhook-interface file ingress Legacy webhook/upload flows can still run through the compatibility path, but new callable ingress should use `:interfaces` and the `installations interfaces` command. ## Rollback Capture pre-change state: - `breyta flows show ` - `breyta flows show --include versions` Rollback path: - `breyta flows promote --version ` - re-run representative verification and record evidence ## Feedback Path Use `breyta feedback send` for product issues, platform bugs, confusing errors, missing docs or commands, and missing capabilities. Tell the user when feedback is worth sending. These reports go directly to the Breyta team. --- Document: Breyta CLI Skill: Outputs And Tables URL: https://flows.breyta.ai/docs/breyta-cli-skill-outputs-and-tables.md HTML: https://flows.breyta.ai/docs/breyta-cli-skill-outputs-and-tables Last updated: 2026-05-15T14:59:18+02:00 # Outputs And Tables Reference Load this reference before creating or editing final outputs, public artifacts, persisted resources, table viewers, or run evidence. ## Output Defaults - Public/end-user flow outputs should default to one readable Markdown artifact, or a deliberate table/media viewer when that is better for the user. - The default rich output pattern is a Markdown report. Use fenced `breyta-resource` blocks when that report needs real Breyta tables, charts, downloads, images, video, nested Markdown, text, or JSON resources. - Keep raw debug maps, internal ids, `res://` refs, and implementation-only fields out of final public output unless the user explicitly asked for them. - Include enough summary context that a user can understand the result without reading step internals. - Include exact failure or skip reasons when that is the user-facing outcome. - Do not paste EDN/JSON maps into Markdown prose. Return them as `:raw` viewer values, embed persisted JSON with `:view :json`, or put supporting values in fenced code blocks. ## Artifact Audience Review After a run returns output or artifacts, inspect the actual user-facing result before calling the workflow done. Review it from the perspective of the flow's intended user and the audience of the artifact, not from the perspective of the flow author. Use the right inspection path: - Markdown/report: open or read the rendered output and check whether the first screen explains the result, the content answers the user's request, links/resources resolve, and there are no raw debug maps or placeholders. - Table: read the table resource, verify it has rows and useful columns, and judge whether the table is scannable for the intended user. - Image: open the image artifact/resource and use vision analysis to check whether the visual content is legible, correctly framed, complete, and aligned with the requested audience/use. - Video: if browser tooling is available, capture screenshots from several moments and review them. Otherwise use the best available frame extraction path and report the limitation. - Download/blob: inspect metadata and, when safe and useful, read or preview the file enough to confirm it is the expected artifact. Audience review should catch empty outputs, cut-off text, broken embeds, illegible images, video frames that never show the intended content, excessive debug detail, wrong tone, missing context, and outputs that satisfy the data contract but fail the end-user experience. ## Output Shape Decision Table | User need | Preferred output shape | |---|---| | Narrative summary or report | `{:breyta.viewer/kind :markdown ...}` | | Short explanatory table inside text | Markdown table inside a Markdown viewer | | Durable grid for scan/copy/export | Persist rows as a table resource, then return `:table` or embed it with `breyta-resource` | | Filtered or aggregated table inside a report | Markdown viewer with `breyta-resource` `:view :table` and bounded query/aggregate options | | Chart from table rows | Markdown viewer with table `:chart` options in the resource fence | | Source/export affordance | Separate `breyta-resource` fence with `:view :download` | | Image/audio/video | Persist a blob and return or embed `:image`, `:audio`, or `:video` | | Structured map or JSON as the product | `:raw` viewer, not Markdown prose | | Supporting structured detail in Markdown | Fenced code block with `clojure`, `edn`, or `json` | Markdown resource embeds are authoring syntax. The end-user output should render as one continuous document; users should not see the raw fenced blocks or technical `res://` URIs. The floating Copy Markdown action exports resolved embeds, not Breyta-only fences, whenever possible. Tables become Markdown tables, JSON/raw resources become readable fenced Clojure blocks, download/video/audio resources become links to the Breyta resource viewer, and image embeds keep an image preview wrapped in a Breyta resource-viewer link. For field-by-field syntax, use the product docs: `breyta docs find "output artifact reference breyta-resource table aggregate"`. ## Full Breyta URLs Report full Breyta URLs whenever the CLI returns them: - flow URL - run URL - run output URL - activation/setup URL - resource URL - public template URL - Discover/install URL Prefer CLI-returned URLs: - `data.flow.webUrl` - `data.run.webUrl` - `run.webUrl` - `outputWebUrl` - `data.webUrl` - `meta.webUrl` - `error.actions[].url` Hand-build URLs only when the CLI does not return one and all required ids are known. ## Markdown Table Or Breyta Table Resource When the user asks for "table output", decide first which output type they mean. Use a markdown table when: - the table is short - the table is explanatory - the table belongs inside a readable report - copy/export/filter behavior is not important Use a Breyta table resource when: - there are many rows - the user needs scan/copy/export behavior - the result should be durable structured data - another step or human should consume the table as a resource When the table belongs in a larger written report, embed the persisted table in Markdown with a `breyta-resource` fence instead of returning a separate table viewer as the whole output. Use a separate `:view :download` fence when the user should get a CSV/source download. ## Breyta Table Resource Verification For a Breyta table resource, verify all of these before calling the workflow done: - final output has `:breyta.viewer/kind :table` - viewer value has `:type :resource-ref` - resource content type is `application/vnd.breyta.table+json` - `preview.rows-written > 0` - `breyta resources read ` returns rows Do not consider `breyta resources workflow list` alone sufficient verification. It proves a resource exists, not that the user-facing table resource is readable and populated. ## Table Write And Read Shapes For `:persist {:type :table ...}` writes, use the table-name form in the `:table` field. Table queries and reads can use table resource refs, but a result table URI in a persist write can pass draft validation and still fail at live runtime parameter validation. After writing rows, read the table back with `breyta resources read ` or the table step/query path that downstream consumers will use. Confirm the returned URI/ref is non-empty and that rows are present before storing or reporting the table as ready. If a flow stores a table URI/ref in KV or a registry, never overwrite a known good value with `nil` or an empty value from a later upsert/write result. Keep the previous ref or fail loudly so downstream reads do not inherit a corrupted registry entry. ## Large Artifact Hygiene Agents should inspect large artifacts through refs and bounded previews, not by pasting full tables or reports into chat. Use `breyta resources read ` for the default compact blob preview or default bounded table preview and add `--full` only when the full payload is required for the current decision. Treat `--pretty` as formatting only; it must not imply full payload access. When a flow produces a long Markdown report, transcript, JSON body, or research artifact, persist the body as a blob/resource and pass the resource ref, signed URL, and a short summary through run results or table rows. Keep durable tables scan-friendly: store status, counts, source refs, output refs, and short previews instead of giant cells like `report_markdown` that contain the whole artifact. For blob resources, choose the tier up front: retained/default for durable or user-visible artifacts; `:tier :ephemeral` for temporary streamed HTTP blobs, downloads, exports, generated media, and intermediate API responses. Function, table, and KV persists use the retained/default path today. ## Persist And Resource-Ref Handoff Default to `:persist` when payload size is unknown or unbounded: provider exports, paged API responses, generated media, long Markdown reports, transcripts, JSON snapshots, files, and tables. Persisting changes the shape passed to later steps: - persisted blobs carry resource refs in `:uri` / `:resource-uri` - persisted blobs also carry storage loader details under `:blob-ref` - persisted tables should be treated as canonical `{:type :resource-ref :uri ...}` maps - UI/API/report handoff should use the resource URI or canonical resource-ref - downstream blob hydration should pass the whole persisted step result and use `:load` on the function step Use this handoff shape: ```clojure (let [resp (flow/step :http :fetch-export {:connection :api :path "/export" :accept :json :persist {:type :blob :tier :ephemeral :content-type "application/json"}}) rows (flow/step :function :extract-rows {:input {:resp resp} :load [:resp] :code '(fn [{:keys [resp]}] (get-in resp [:body :items]))}) table (flow/step :function :persist-rows {:input {:rows rows} :code '(fn [{:keys [rows]}] {:rows rows}) :persist {:type :table :table "export-rows" :rows-path [:rows] :write-mode :upsert :key-fields [:id]}})] {:breyta.viewer/kind :table :breyta.viewer/value table}) ``` Use `(:resource-uri result)` or `(:uri result)` when a command, table row, or Markdown `breyta-resource` fence needs a URI string. Keep the full persisted result when a downstream step can load it. Do not use `[:blob-ref :path]` as a public/user-facing ref; it is a storage pointer for loaders and storage-backed steps. ## Persisted Resources Use `breyta resources ...` to inspect persisted result refs like `res://...`. - `breyta resources list` lists refs by filters. - `breyta resources workflow list ` lists refs for a run. - `breyta resources get ` fetches metadata. - `breyta resources read ` reads persisted results. - `breyta resources url ` gives a shareable URL. For large outputs, pass refs through the flow rather than large bodies. Keep inline final output small and user-facing. ## Runtime Proof Runtime proof should include: - exact command run - workflow/run id - terminal status - user-facing output summary - artifact audience review notes - required side-effect evidence - full Breyta URLs - unresolved risk or blocked verification Do not report success based only on `flows push` or `resources workflow list`. Behavior is proven by a representative run, by reading the outputs/resources that matter, and by reviewing the actual artifact as the intended user would. For notification flows, include the rendered or submitted payload in the proof: recipient, sender, subject, relevant body copy, and any required absence checks such as "no PR link" or "no debug map". --- Document: Breyta CLI Skill: Provider And API Freshness URL: https://flows.breyta.ai/docs/breyta-cli-skill-provider-api-freshness.md HTML: https://flows.breyta.ai/docs/breyta-cli-skill-provider-api-freshness Last updated: 2026-05-21T16:29:37+02:00 # Provider And API Freshness Reference Load this reference before creating or editing external API calls, auth shape, provider config, rate-limit assumptions, LLM provider settings, or model ids. ## Core Rule Training data is typically stale for current API behavior. Verify against the current source of truth before choosing endpoints, request bodies, auth, limits, or models. Use: - current official provider docs - provider API references - provider model-list APIs when credentials/tooling are available - relevant Breyta docs and approved templates - a draft run or isolated step run when feasible ## Applies To All Providers This rule is not only for OpenAI. Apply it to: - OpenAI - Anthropic/Claude - AWS Bedrock Claude - Google/Gemini - Chat Completions-compatible providers, including legacy `:openai-compatible` integrations - OpenRouter - HTTP APIs - databases - SaaS APIs - any vendor a flow calls ## Connection And Credential Gate Before third-party calls, classify credential ownership and put durable credentials in `:requires`, not invocation text. Check existing connections; if none fits, hand off the Breyta setup/connection URL. Do not ask for secrets in chat. Protected setup/edit links should return through login. ## Model Selection - Before adding or changing model ids, check the provider's current model docs or model-list API when credentials/tooling are available. - Verify the exact model id with a draft run or isolated step run before release when feasible. - For OpenAI-backed steps, use `gpt-5.4` as Breyta's current API default where a default is needed, but still verify availability in the target environment. - Do not claim or use unreleased provider models without provider/API proof. - Preserve explicit user requests, such as `gpt-5.4` or a specific Claude/Gemini model, unless current provider docs/API availability show the model is unavailable or unsuitable. - When editing existing flows, keep legacy models/APIs only if compatibility, cost, or evaluation history is intentional. Otherwise propose upgrading to the current verified provider/API choice. ## OpenAI Connection Defaults For OpenAI-backed `:llm` and `:agent` steps, use an `:http-api` requirement with an OpenAI backend connection. The base URL should include `/v1`, and the connection config must be a map rather than `nil`. ```clojure {:requires [{:slot :ai :type :http-api :provided-by :author :label "OpenAI" :base-url "https://api.openai.com/v1" :auth {:type :api-key}}]} ``` Use `:provided-by :installer` instead when every installer should bring their own OpenAI account. For raw HTTP calls to OpenAI instead of the LLM provider backend, use the same `https://api.openai.com/v1` base and bearer-style `Authorization` semantics from OpenAI's API reference. ## AWS Bedrock Connection Defaults For Bedrock-backed Claude calls, use an `:http-api` requirement with `:backend :bedrock` and AWS SigV4 auth. Keep region/service metadata in the auth config and store credentials in the referenced secret. ```clojure {: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"}}]} ``` Use a Bedrock Claude model id in `:llm`, for example `"anthropic.claude-3-5-sonnet-20241022-v2:0"` or a region/inference-profile id such as `"us.anthropic.claude-3-5-sonnet-20241022-v2:0"`. Verify the model id against the target AWS region before release. For Bedrock Claude image input, use `:type :image-resource` for uploaded or persisted files. Bedrock's Claude Messages API expects base64 image content blocks, so do not treat `res://...` values or ordinary HTTPS image URLs as provider-native Bedrock image URLs. For Bedrock Claude Sonnet 4.5 and Haiku 4.5 model ids, choose either `:temperature` or `:top-p`; do not set both on the same request. ## API Shape Checks Before changing an API integration, verify: - endpoint path and base URL - method and content type - auth method and token scope - required headers - request body shape - pagination and limits - retry and rate-limit behavior - response shape and error shape - idempotency keys for side effects - webhook signature or secret requirements If docs and examples disagree, treat current official provider docs/API reference as the source of truth and call out the mismatch. ## Breyta Checks Search Breyta docs/examples for the integration surface before editing: - `breyta docs find "source:cli connections test"` - `breyta docs find "source:flows-api http request"` - `breyta docs find "source:flows-api llm model"` - `breyta flows search " " --limit 5` - `breyta flows grep "" --tool-name ` - `breyta flows templates search " " --limit 5` Use approved templates to learn local structure, but do not copy stale endpoint or model assumptions without verifying provider docs. ## Reporting In the final report, state: - which provider/API docs or model-list API were checked - the selected model or endpoint - whether the selection was verified by a draft run or isolated step run - any remaining availability or compatibility risk --- Document: Breyta CLI Skill: Public Flows URL: https://flows.breyta.ai/docs/breyta-cli-skill-public-flows.md HTML: https://flows.breyta.ai/docs/breyta-cli-skill-public-flows Last updated: 2026-05-20T16:17:13+02:00 # Public Flows Reference Load this reference before creating, editing, testing, releasing, promoting, or describing public/end-user Breyta flows. ## Public-Ready By Default Assume every new or edited flow may become public unless the user explicitly says it is internal-only. This changes how the flow is built, not whether it is published. Public-ready defaults: - use `:requires`, setup inputs, run inputs, connection slots, templates, and resources for values that vary by installer, workspace, user, region, model, file, or environment - avoid hardcoded workspace ids, user ids, emails, test resource ids, private URLs, secrets, personal accounts, and one-off local assumptions - keep outputs and timelines understandable without source code - make names, tags, descriptions, and install copy useful to the target end user Hardcoding is acceptable only for stable public facts or documented API constants. When a value belongs to the author, installer, or run, model it as configuration. ## Public Surfaces Are Separate - Private workspace flow: visible inside a workspace. - `breyta flows search`: actual workspace flow metadata. - `breyta flows grep`: actual workspace flow source/config search. - `breyta flows templates search/grep`: approved reusable template definitions to inspect and copy from. - `breyta flows discover list/search`: public installable catalog. - Discover visibility: stored metadata or `:discover {:public true}`. - Marketplace visibility: stored metadata or `:marketplace {:visible true}`. - Paid app catalog: stored metadata or source-authored `:marketplace {:app {... :monetization {:plans [...]}}}`. Legacy `:marketplace {:monetization ...}` is compatibility for existing listings, not the preferred shape for new paid apps. - Installation: a per-user instance of an end-user flow with user-scoped prod bindings and inputs. - Activation/setup URL: the author or workspace setup surface for bindings and saved setup inputs. It is not the public install dialog. - Discover install dialog: the public/end-user install surface. This is the surface that proves an installable flow can be installed by an end user. Do not use `flows templates search` as proof that a flow is publicly installable. Do not use `flows discover` as proof that a private workspace flow exists. ## Public Flow As Reusable Tool When another Breyta flow, hosted agent, coding agent, or external client should reuse a public flow, prefer the installed HTTP or MCP interface over author draft/live endpoints. Default reuse loop: 1. Find the flow in Discover with `breyta flows discover search`. 2. Create, configure, and enable an installation in the caller workspace. 3. Inspect callable surfaces with `breyta flows installations interfaces `. 4. Smoke-test through the installation: `breyta flows interfaces call --installation-id --input '{"key":"value"}' --wait`. 5. Use the installation-scoped HTTP or MCP endpoint from the wrapper flow or external client: `/api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}`. Use installation-scoped endpoints for consumers so installer setup, auth, billing entitlement, and ownership are applied correctly. Do not hand off author draft/live interface URLs as the normal consumer integration path. For MCP clients, connect to the installed Streamable HTTP endpoint with bearer auth, call `initialize`, then `tools/list`, then `tools/call` with the published tool name. Treat paid-flow errors such as `billing_trial_ended` as billing or trial state, not as proof that the interface contract is broken. ## Install And Public App Links Do not guess install links from the current authoring page context. In particular, do not treat an activation/setup URL such as `/activate` as the public install page unless the docs or CLI output identify it as the requested install surface. Distinguish these surfaces when handing off public or installable work: - Authoring flow page: where the creator edits, releases, configures, and runs the flow inside the source workspace. - Activation/setup page: where a workspace configures an existing flow target or installation. This is setup, not proof of public installability. - Internal app/install surface: where a workspace user can install, configure, or run an installed app inside Breyta, commonly under the workspace Apps area. - Discover/install surface: where public installable flows are found and installed after Discover visibility is enabled. - Public marketing app page: for public flows, also provide `https://breyta.ai/apps/` so the creator can view, share, and market the app. When the user asks to make a flow installable: - first confirm the requested distribution: internal installable, public Discover installable, monetized, or marketplace-visible - do not treat "make this a public app" as permission to expose it to all Breyta users; ask directly before turning on Discover or marketplace visibility - load this reference before changing release, install, Discover, marketplace, or public copy behavior - release/promote only after explicit approval when required by the approval gate - enable Discover/public visibility only after the user has approved that distribution - verify the install surface through CLI/docs/UI evidence rather than inferring from the page the user happened to be viewing - when CLI/API support the approved public, pricing, listing, Discover, or marketplace change, do it directly instead of sending the user to the publish page - for new paid apps, keep plan ids stable, use at most one default/recommended plan, and do not model seat pricing unless explicit seat entitlements exist - report every relevant URL returned by Breyta: authoring flow, setup/activation, install surface, Discover/public listing, public marketing app page, run, and run output ## Approval Gate Never set Discover or marketplace visibility without explicit flow-author approval. The approval must say that the flow may be made accessible to all Breyta users now. A vague request to "make it public" or "make this a public app" is not enough. Do not do any of the following without approval: - set `:discover {:public true}` - set `:marketplace {:visible true}` - run equivalent CLI update flags for public visibility - release or promote a public/end-user flow - publish a new public description, media, or install-facing copy that materially changes user claims When approval is needed, show the exact metadata and release/promotion command you plan to run. Ask: "Do you want me to make this flow accessible to all Breyta users now?" Only proceed after the author confirms and the flow is installable-ready. ## Installable Flow Checklist Use this checklist whenever the user asks for an installable, public, Discover, or marketplace flow. Do not stop at activation. 1. Confirm explicit flow-author approval for public visibility, release/promote, install-facing copy, media, and marketplace visibility. 2. Configure Discover visibility with `:discover {:public true}` and `breyta flows push --file ...`, or `breyta flows discover update --public=true`. 3. Push, diff, release, and promote the live version after approval. 4. Prove owner setup separately with activation/configure/check when required. 5. Prove end-user installability through the Discover install dialog or an installation create/configure/enable path. 7. Bind installer-owned connections and setup inputs on the installation. 8. Run the installed target with `breyta flows run --installation-id --wait`. 9. Inspect the installed run output from the intended end-user perspective. Report URLs for the flow, live version, activation/setup surface, Discover or install surface, installation, installed run, run output, and any resources the user needs to inspect. If browser access is unavailable, write `web UI not verified` and include the CLI installation proof that was run. ## Public Preflight Checklist Use public-ready architecture for every new or edited flow unless the user says it is internal-only. Run this full checklist only when the task changes public copy, install behavior, publish/release behavior, Discover, marketplace, or public output. Before turning a flow public or changing public behavior, confirm these essentials: - public-ready architecture: no hardcoded private workspace/user/test values, secrets, private URLs, or author-only assumptions - setup contract: installer-owned connections use the right `:requires` shape, including `:provided-by :installer` and `:oauth` where OAuth connect-first setup is needed - cost contract: external API, LLM, search, and other paid provider steps have step-level metering or cost estimates where supported; if unsupported, record that in the risk ledger - public name and slug: clear, human-readable, and aligned with the primary user query - short `:description`: one or two sentences covering audience, entrypoint/interface, integrations, and outcome - meta description copy: 140-160 character search snippet for review; set it only if current docs/schema expose a supported field - markdown publish description: SEO/AEO-friendly end-user landing-page copy that explains why to install, setup requirements, what happens after install, outputs, limits, and failure/permission expectations - tags: outcome, audience/use case, integration/tool, and modality tags, not only `draft` or internal labels - media: useful image or video when it helps users understand the actual result; `flow-resource` media must be a reviewed same-workspace `res://...` resource intended for public cards - outputs: a readable public result, usually markdown, table/media viewer, or markdown with embedded Breyta resources - verification: draft run, public/install setup path, installation-targeted run when relevant, web UI check when available, output/resource readback, and full Breyta URLs - approval: explicit flow-author approval for visibility, release/promote, marketplace, media, and install-facing claims ## Setup And Run Field Planning Treat setup and run forms as product UI for a non-technical end user. Before creating or editing public flow fields, classify every user-supplied value: - setup-once: company profile, audience, voice, examples, default region, default folder, or other durable context reused across runs - run-each-time: prompt, file, CSV, resource picker selection, recipient, date range, row limit, or other values that naturally change per run - connection: OAuth/API/database/blob-storage accounts that should be bound through `:requires` - hidden/internal: implementation ids, debug toggles, author workspace state, or values the end user should not decide Third-party accounts are setup work, not prompt text: declare connection requirements, hand off setup/edit URLs when needed, and never ask end users to paste secrets in chat. For setup-once context that needs LLM processing, use setup inputs to create a durable artifact such as a company profile, voice profile, influencer bible, or other reusable context, then persist and reuse that context on later runs. Keep the setup page and run form small: - use plain labels and helper text - set defaults where safe - avoid advanced/internal fields unless they are real user decisions - use `:label` and `:title` on run-collected forms so recent runs are readable - prefer `:resource` fields for selecting existing artifacts, and keep manual upload available when useful ## Metadata Is A Landing Page For public/end-user flows, treat metadata and install surfaces as an end-user landing page, not internal implementation notes. - `:description` is the short card/catalog summary. It should say who the flow is for, what starts it, what systems it uses, and what outcome it produces. - Tags should support discovery and clustering by outcome, audience/use case, integration/tool, and modality. - `publishDescription` or `--publish-description-file` is richer markdown for install/discover dialogs. It should explain why to install, what the user must connect or enter, what happens after install, and what output they get. - Curated Discover card media should help the user understand the actual flow or result, not decorate the listing. - Do not invent new metadata fields such as `metaDescription` unless product docs or schema support them. Write the markdown publish description like a small landing page for the intended end user: - lead with the outcome and audience - name the entrypoint/interface and connected systems in plain language - explain required setup, permissions, and inputs before the user installs - describe the output they will receive and where they will see it - include practical limits, privacy/permission expectations, and common failure cases - use headings and short sections so search engines, AI answer engines, and humans can extract the value quickly Good public copy answers: 1. Who is this for? 2. What entrypoint or interface starts it? 3. What accounts, files, or inputs must the user connect? 4. What does it do after install? 5. What output does the user get? 6. What failure or permission cases should the user expect? ## Testing Public Behavior When public behavior matters, test through public surfaces: - use `breyta flows discover list/search` for catalog visibility - use `breyta flows discover search "" --include-own` when debugging whether a flow owned by the current workspace is indexed, because normal Discover search may exclude own flows - verify the Discover install dialog; `/activate` alone is only owner setup proof - install or inspect installation surfaces rather than only running the private draft flow - verify activation/bindings in the user-scoped prod profile - run an installation-targeted execution when required: - `breyta flows run --installation-id --wait` - after release/promote approval, smoke the installed target: - `breyta flows run --target live --wait` - when browser/UI access is available, test the actual setup page, run form fields, upload CSV or file path, resource picker, and run output page - report full Breyta URLs for the public flow, install surface, activation, run, and run output when available Private draft runs are useful for build iteration, but they are not sufficient proof that public install behavior works. End-user verification ladder: 1. Draft run for fast iteration. 2. `breyta flows diff ` before release. 3. Release/promote only after explicit flow-author approval. 4. `breyta flows show --target live`. 5. `breyta flows run --target live --wait`. 6. Discover install dialog or installation create/configure/enable proof. 7. `breyta flows run --installation-id --wait`. 8. Web UI setup/run test when tooling and access are available. 9. Resource picker/upload test for resource fields, including upload CSV workflows. 10. Output artifact audience review. If a browser or UI path is unavailable, do the installation-targeted CLI proof and write `web UI not verified` in the risk ledger. If CLI works but UI fails, or draft works but the setup page, run form fields, installed flow, resource picker, or old live version fails, submit `breyta feedback send` with full Breyta URLs before closing the task. For final handoff, do not write `Done` for public, installable, marketplace, or paid-flow work until the touched user-visible surfaces have been verified or named as unverified. The minimum surface matrix is source flow, live version, Discover listing/install surface, marketplace visibility when touched, activation/setup when relevant, installed run when install behavior matters, public app page, and run output. ## Human Timeline Quality Public users and operators read run timelines without source code. Keep labels plain and outcome-based. - Add `:title` values for non-trivial or user-visible steps. - Use step titles that describe a human action or outcome. - Use clear manual interface labels and branch labels. - Avoid vague visible labels such as "normalize", "hydrate", "finalize", "process", or "prepare payload" unless that is truly what the user understands. Prefer titles such as: - "Search Gmail for support emails" - "Create draft reply" - "Check whether invoice is already approved" - "Summarize run result" ## Discover Card Media For public Discover/install cards, use creator-curated media when it helps the user understand the actual result. - Set media with `breyta flows update --publish-media-type image --publish-media-source-kind https-url --publish-media-source https://...`. - Video media can include a poster with `--publish-media-poster-kind https-url --publish-media-poster https://...`. - Clear media intentionally with `breyta flows update --clear-publish-media`. - If keeping the flow in source control, author the same value in the flow file as `:publish-media`. - Use alt text that explains the visible result, not implementation detail. - For `--publish-media-source-kind flow-resource`, use a `res://...` URI from the same workspace. Treat it as public card media: use only reviewed resources that are meant to appear on Discover/install surfaces. --- Document: Breyta CLI Skill: Reliability And Concurrency URL: https://flows.breyta.ai/docs/breyta-cli-skill-reliability-and-concurrency.md HTML: https://flows.breyta.ai/docs/breyta-cli-skill-reliability-and-concurrency Last updated: 2026-05-16T07:04:10+02:00 # Reliability And Concurrency Reference Load this reference before creating or editing fanout, concurrency, waits, retries, idempotency, large artifacts, checkpoints, or failure behavior. ## Reliability Planning Before push: - define every side effect and what must happen exactly once - name the idempotency or duplicate-protection key for each side-effectful path - classify every external call as retryable or fail-fast - set timeout expectations for each external boundary - choose concurrency intentionally and state why - define payload strategy: inline small data, persist large artifacts, pass refs for large blobs - define cursor/checkpoint behavior for partial failure and replay - define exact runtime proof: result fields, counters, child runs, or resources that prove success ## Concurrency Defaults - Use sequential behavior when order matters, when mutating shared state, when moving large artifacts, or when external systems are slow or fragile. - Use fanout only for independent, bounded, side-effect-safe items with explicit timeout awareness. - Use keyed concurrency when work must serialize per entity while allowing cross-entity parallelism. - If concurrency is not clearly beneficial, default to sequential. - `flows run` returns `concurrencyDecision` and `concurrencyKey`; use those fields plus run timing to prove whether a run started, coexisted, queued, blocked, or was rejected. Answer these before build: 1. What can run in parallel safely? 2. What must never overlap? 3. What shared state or side effect could be corrupted? 4. What is the per-item timeout? 5. What happens when one item stalls? 6. How are partial successes summarized without losing failed items? ## Keyed Concurrency And Raw Entrypoints For webhook, event, and interface-started flows, `:concurrency {:type :keyed ...}` is evaluated before the flow body can normalize input. The `:key-field` must exist in the raw entrypoint payload that starts the run, not only in a derived `request_key` or normalized function output. Use a root field when the provider gives one. For a GitHub pull request webhook, `:number` is a safer key than a later normalized `:request-key`. If the key is nested, use the raw nested path, such as `[:pull_request :id]`, and prove it with a real webhook-shaped payload before release. ## Fanout Use fanout for bounded independent work. Avoid fanout for large file transfer, cursor advancement, or shared-state mutation unless docs and tests show it is safe. For fanout flows: - keep item payloads small - persist large artifacts and pass refs - include a per-item result shape with success/failure fields - summarize counts and failures in final output - prove the chosen mode with evidence: counts, failures, child runs, and no skipped/reprocessed items - when fanout starts child workflows, group the parent and child flows with shared flow metadata so users can see the relationship; grouping is only display metadata, not runtime orchestration ## Paging And Loops Breyta can handle paging and looping, but loops must be explicit, bounded, and checkpoint-aware. Do not assume a single provider call is enough for large datasets, and do not build unbounded "keep fetching until done" flows. Preferred paging shapes: - one packaged `:steps` wrapper or `:function`-backed step that pages a provider API up to `max-pages` / `max-items`, persists each page or final rows, and returns counts plus resource refs - table resource paging with `:table {:op :query ... :page {:mode :cursor ...}}` and explicit `:sort` - multiple small runs or a child flow when each page is heavy or side-effectful - cursor/checkpoint state that advances only after the page's durable writes or side effects have succeeded Use `flow/poll` for external async job completion, not generic data paging. If a loop includes repeated `flow/step` calls, keep the bound small and consider rate-limit pauses with `:sleep`. For unknown/unbounded data, persist page results and pass resource refs instead of carrying accumulated bodies inline. ## Fixed Delays Use `:sleep` for timer delays, deployment-lag buffers, polling gaps, or rate-limit spacing. Use `:wait` only when the run should pause for an external signal, webhook, CLI action, or human action. Example: ```clojure (flow/step :sleep :deployment-lag {:duration "10m"}) ``` ## Retries And Checkpoints - Retries should be bounded and reserved for transient failures. - Never advance cursors/checkpoints past failed work. - Resume/replay behavior must be explicit for partial success paths. - Rerun once when feasible to verify idempotency and duplicate protection. ## Large Artifacts - Persist large artifacts and pass resource refs. - Prefer child flows to isolate heavyweight artifact creation and handoff. - Avoid moving large bodies through many steps. - Use `breyta resources get`, `breyta resources read`, and `breyta resources url` to prove artifact availability. ## Verification Before release, verify: - happy path - no-item or no-op path when applicable - partial failure or retry path when feasible - replay/rerun behavior for duplicate protection - output/resource evidence for the user-facing result After release, capture live smoke proof when side effects are safe. --- Document: Core Concepts URL: https://flows.breyta.ai/docs/reference-core-concepts.md HTML: https://flows.breyta.ai/docs/reference-core-concepts Last updated: 2026-05-10T21:06:10+02:00 # Core Concepts ## Quick Answer Canonical lifecycle vocabulary: - Working copy: editable authoring state used by `pull/push/validate`. - Draft target: default bindings/config target after push. - Run: one execution using draft-target bindings/config by default, with code version resolved from the active release unless explicitly overridden. - Release/promote/installations: advanced rollout model for explicit promotion and scoped runtime targets. ## Concepts - Flow definition: versioned EDN map describing setup requirements, invocation inputs, interfaces, schedules, steps, and orchestration. - Draft target: default bindings/config target for `flows run `. - Installation: advanced runnable target for a flow (live or installation-specific instances). - Bindings/config: saved values for `:requires` setup slots and setup form fields. - Invocation inputs: values supplied each time the flow is run, declared under `:invocations`. - Promote: advanced operation to assign a release to the live installation target (`flows promote`). - Interfaces: manual, HTTP, webhook, and MCP surfaces over invocation contracts. - Schedules: time-based automation over invocation contracts. - Triggers: deprecated legacy source for old manual, schedule, and webhook/event definitions. - Versioning: releases are immutable; runs are pinned to the resolved release at start time. - Run concurrency: `:concurrency` controls overlap and version handoff behavior. ## Requires Vs Bindings | Concept | Meaning | |---|---| | `:requires` | Setup contract declared in the flow definition (`slots`, types, auth/setup input expectations). | | `:invocations` | Per-run input contract declared in the flow definition. | | Bindings/config | Concrete target-specific values mapped to those requirements (`draft`, `live`, installation). | | Configure check | Readiness check for missing/invalid bindings and required inputs before run. | | Usage tracking | `connections usages` / `secrets usages` inspect binding references across targets. | Delete guard behavior follows binding references, not just flow source text. ## Related - [Start Here](/docs/start-here) - [CLI Workflow](/docs/guide-cli-workflow) - [Installations](/docs/guide-installations) - [Flow Definition](/docs/reference-flow-definition) - [Run Concurrency](/docs/reference-run-concurrency) - [CLI Commands](/docs/reference-cli-commands) --- Document: CLI Workflow URL: https://flows.breyta.ai/docs/guide-cli-workflow.md HTML: https://flows.breyta.ai/docs/guide-cli-workflow Last updated: 2026-05-21T12:24:31+02:00 # CLI Workflow ## Quick Answer Default flow lifecycle: 1. Edit working copy (`pull`/`push`/`validate`) 2. Verify runtime (`run`) 3. Use `release`/`promote`/`installations` only when you need explicit rollout governance ## Default Reporting Step If work is blocked by missing capability or unclear failures, report it before ending the task: ```bash breyta feedback send \ --type issue \ --title "Short summary" \ --description "What failed, expected behavior, and workaround tried" \ --flow --workflow-id --run-id --agent ``` Use `--type feature_request` for missing product capability. ## Canonical Commands | Stage | Command | |---|---| | Inspect workspace flows and metadata | `breyta flows search "" --limit 5`; `breyta flows show ` for the nearest match; `breyta flows list --limit 10` only when you need a workspace-wide list | | Search workspace source/config literals | `breyta flows grep "" --surface definition,tools --limit 5 [--or ]` | | Discover approved reusable templates to copy from | `breyta flows templates search "" --limit 5`; `breyta flows templates grep "" --surface steps,tools --limit 5` | | Reuse existing data/resources | `breyta resources search "" --limit 5`; `breyta resources read --limit 5`; `breyta resources upload ./hero.png --print-uri` | | Browse public installable flows for this workspace | `breyta flows discover list`; `breyta flows discover search ""` | | Reuse/create/test connections (when external systems are involved) | `breyta connections list`; `breyta connections create ...`; `breyta connections test ` | | Pull local file | `breyta flows pull --out ./tmp/flows/.clj` | | Push working copy | `breyta flows push --file ./tmp/flows/.clj` | | Suggest config wiring | `breyta flows configure suggest ` | | Configure draft target (when required) | `breyta flows configure --set api.conn=conn-123 --set activation.region=EU` | | Configure live target for a specific release (advanced) | `breyta flows configure --target live --version --set api.conn=conn-123` | | Promote current draft setup to live target (advanced) | `breyta flows configure --target live --from-draft --version ` | | Update installation activation inputs (advanced) | `breyta flows installations configure --input '{"region":"EU"}'` | | Update marketplace visibility | `breyta flows marketplace update --visible=true` | | Update public discover visibility | `breyta flows discover update --public=true` | | Update grouping, discover card media, or display-icon metadata | `breyta flows update --group-key billing --group-name "Billing"`; `breyta flows update --publish-media-type image --publish-media-source-kind https-url --publish-media-source https://...`; `breyta flows update --primary-display-connection-slot crm` | | Validate working copy | `breyta flows validate ` | | Run and wait | `breyta flows run --input '{"n":41}' --wait`; add `--interface-id ` only when selecting the declared manual interface explicitly | | Inspect active incidents | `breyta incidents list --status open`; `breyta incidents list --status open --mine`; `breyta incidents show ` | | Inspect affected lanes/keys | `breyta incidents lanes ` | | Change operator disposition | `breyta incidents acknowledge `; `breyta incidents snooze --for 2h`; `breyta incidents suppress ` | | Inspect digest artifacts and delivery state | `breyta digests list --kind scheduled --cadence monthly`; `breyta digests show `; `breyta digests deliveries ` | | Change digest cadence | `breyta digests cadence`; `breyta digests cadence set monthly` | ## Authoring Loop (Working Copy) 1. `breyta flows pull --out ./tmp/flows/.clj` 2. Edit file 3. `breyta flows push --file ./tmp/flows/.clj` 4. If the flow has required slots/inputs: `breyta flows configure ...` 5. `breyta flows configure check ` For large local source files, `breyta flows push` also expands explicit local include forms before upload: ```clojure :functions [#flow/include "flow-assets/functions/normalize-config.edn"] :templates [#flow/include "flow-assets/templates/security-reviewer.edn"] ``` The include path is resolved relative to the flow file you push. Expansion happens only in the uploaded payload; your local source file keeps the include form. `connections test` is a health/config check. It does not replace a real flow run against the target system. If you are updating live after a slot change, use version-pinned live checks/applies: - `breyta flows configure check --target live --version ` - `breyta flows configure --target live --version --set ...` For a brand-new flow with required slots, use `--version latest` on the live target before the first release. Otherwise `flows release ` can fail with `live_config_incomplete`. Optional checks: - `breyta flows validate ` - `breyta flows show ` - `breyta flows diff ` Public discover notes: - Public discover is stored metadata, not just a UI interpretation of the flow file. - Marketplace visibility is stored metadata. You can author `:marketplace {:visible true}` in source or change it explicitly with `breyta flows marketplace update --visible=true`. Ask for explicit flow-author approval and prove installable readiness before enabling it. - Paid app monetization can be saved from the flow Publish page or authored in source with `:marketplace {:app {... :monetization {:plans [...]}}}` and persisted on push. Legacy flow-level `:marketplace {:monetization {...}}` remains supported for existing listings. - Paid public-flow monetization supports `:free`, `:one-time`, `:subscription`, `:usage`, and `:subscription-usage` pricing metadata. Usage pricing records run allowances in metadata while run metering and Stripe usage invoicing are rolled out. - Seat-based pricing is not implemented; use run quantities only for usage-priced plans. - File-based authoring can set `:discover {:public true}` directly in the `.clj` flow source. Do not push source that contains `:discover {:public true}` or `:marketplace {:visible true}` until the flow author has approved broad public access and installable readiness is proven. - File-based authoring can also set `:publish-media` directly in the `.clj` flow source. - Use `breyta flows discover list` / `breyta flows discover search ` to inspect the public installable catalog that appears in the web app. These commands exclude flows owned by the current workspace by default because they show what this workspace can install. - Use `--include-own` only when debugging whether your own public flow is indexed. Verify the buyer/install experience from another workspace. - Use `breyta flows discover update --public=true` when you want to enable discover visibility explicitly after push. Ask for explicit flow-author approval and prove installable readiness before enabling it. - Public discover requires explicit public visibility and an active released version with an installable interface. - `breyta flows templates search/grep` is a different catalog: it returns approved reusable templates to inspect and copy from. Public flow checklist for CLI/source work: 1. If the flow is a new paid app, author `:marketplace {:app {... :monetization {:plans [...]}}}` in source before push. Keep `:marketplace {:monetization {...}}` only for an existing legacy listing. 2. Add install-surface copy and discover card media when the public surface should be polished: - `breyta flows update --publish-description-file ./publish-description.md` - `breyta flows update --publish-media-type image --publish-media-source-kind https-url --publish-media-source https://...` 3. Choose one marketplace visibility path: - Source-first: add `:marketplace {:visible true}` to the flow definition before push only after explicit approval. - Explicit after push: push first, then run `breyta flows marketplace update --visible=true` only after explicit approval. 4. Choose one discover 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 --public=true` only after explicit approval. 5. Push the flow with `breyta flows push --file ...`. 6. If you chose an explicit marketplace/discover path, run those update commands after push. 7. Release/promote it so there is an installable live version. 8. Verify the web Discover install path. For CLI indexing checks, use another workspace or add `--include-own` only while debugging the owner workspace. For the app-first paid-flow path, seller onboarding, and the dedicated Publish page, see [Paid Public Flows](/docs/guide-paid-public-flows). If a flow is public in source but not visible in Discover, the first things to check are: - `breyta flows show --pretty` includes `discover.public` - the flow has an active released version that can be installed Grouping notes: - `breyta help flows update` shows the writable grouping fields and exact clear syntax. - `flows list` exposes `groupKey`, `groupName`, `groupDescription`, and `groupOrder` when a flow is grouped. - `flows show` also exposes `groupFlows`, which lists sibling flows in the same group. - Set explicit order with `breyta flows update --group-order ` when grouped flows should render in execution order. - Lower `groupOrder` values sort first. Use spaced values like `10`, `20`, `30`. - Clear only the order with `breyta flows update --group-order ""`. - Use the existing flow metadata update surface to maintain grouping metadata, then verify with `flows show `. - Grouping metadata is mutable workspace metadata and does not round-trip through `flows pull` / `flows push` source files. - For a focused grouped-flow guide with examples, see [Flow Grouping](/docs/guide-flow-grouping). Display icon notes: - `primaryDisplayConnectionSlot` chooses which connection/provider icon is shown anywhere the flow icon renders today. - In the UI this is labeled `Display icon`. - Set it from the flow detail UI or with `breyta flows update --primary-display-connection-slot `. - Clear it with `breyta flows update --primary-display-connection-slot ''`. - Selectors come from declared `:requires` slots or normalized keys from `:connections`. - If unset or unmatched, rendering falls back to the first explicit `:connections` icon, then the first inferred `:requires` icon. - `flows show --pretty` JSON responses include `primaryDisplayConnectionSlot` plus `_hints` whenever the flow exposes connection metadata or a saved display selector. Example: ```json { "ok": true, "data": { "flow": { "flowSlug": "customer-support", "primaryDisplayConnectionSlot": "crm" } }, "_hints": [ "Inspect `requires`, `connections`, and `primaryDisplayConnectionSlot` with `breyta flows show customer-support --pretty`.", "Set the rendered flow icon with `breyta flows update customer-support --primary-display-connection-slot `.", "Clear it with `breyta flows update customer-support --primary-display-connection-slot ''`.", "If unset or unmatched, the icon falls back to the first explicit `connections` icon, then the first inferred `requires` icon." ] } ``` Discover card media notes: - Use `breyta flows update --publish-media-type ...` when public cards should show a curated image or video preview - Use `--publish-media-source-kind https-url|flow-resource` plus `--publish-media-source ` to set the hero asset - Use `--publish-media-poster-kind` and `--publish-media-poster` only for video media - Use `--clear-publish-media` to remove stored discover card media - If you keep discover card media in source control, author it as `:publish-media` in the flow file and push it - Use `breyta help flows update` for the exact flag surface ## Advanced Release + Install Operations Use `flows release` / `flows promote` / `flows installations ...` only when you need explicit scopes, promotion control, or installation-specific webhooks. | Task | Command | |---|---| | Inspect draft vs live source before release | `breyta flows diff ` | | Create release (default also promotes live and track-latest installations) | `breyta flows release --release-note-file ./release-note.md` | | Create release without promoting end-user installations | `breyta flows release --skip-promote-installations` | | Update a version release note later | `breyta flows versions update --version --release-note-file ./release-note.md` | | Promote live + track-latest installations explicitly | `breyta flows promote ` | | List installations | `breyta flows installations list ` | | Inspect creator install stats/events | `breyta flows installations stats `; `breyta flows installations events --limit 10 --since 7d` | | Create installation | `breyta flows installations create --name "My install"` | | Show installation | `breyta flows installations get ` (includes `installedVersion`, `latestAvailable`, `updateAvailable`, `policy`) | | Configure inputs | `breyta flows installations configure --input '{"region":"EU"}'` | | Enable/disable installation runability | `breyta flows installations enable ` / `breyta flows installations disable ` | | List callable interfaces and endpoints | `breyta flows installations interfaces ` | | Upload files to a webhook interface | `breyta flows installations upload --file ./a.pdf` | New end-user installations follow the latest released version by default. Zero-setup installations can auto-enable immediately; installations with setup requirements stay disabled while setup is incomplete, then start only after the installer enables them. Disabling an installation is a global pause. It blocks manual runs, interface calls, and schedules for that installation, but keeps the saved setup, per-interface settings, and per-schedule settings so they can resume when the installation is enabled again. ## Release Defaults `breyta flows release ` always promotes the live target for the selected workspace and, by default, also promotes track-latest end-user installations. Attach a markdown release note whenever you know what changed. It becomes part of the version metadata returned by `flows show` / `flows versions list`. If the flow has required slots and no active version yet, configure the live target first with `--target live --version latest` before running `flows release`. ## Runtime Verification - Canonical: `breyta flows run --wait` - Advanced explicit target: `breyta flows run --installation-id --wait` - Inspect run: `breyta runs show ` - Filter runs: `breyta runs list --query 'status:failed flow:'` ## Flow Health Delivery Timing - Flow-health incident/digest commands are currently available to workspace creators/admins. - `breyta digests cadence` updates only your own delivery settings in the current workspace. - Scheduled digests summarize surfaced incident changes from the selected `daily`, `weekly`, or `monthly` window. - If there are no surfaced incident changes in that window, no scheduled digest is sent. - Urgent incident updates are delivered as soon as possible when your email delivery mode includes urgent issues. - Urgent emails are not delayed to the next scheduled digest. - Use `breyta digests cadence` or `breyta digests cadence set daily|weekly|monthly` to inspect or change the scheduled cadence from the CLI. `flows run` uses draft-target bindings/config by default, but the executed code version still comes from the active release unless you override it with `--version`. `runs list` uses the same structured filter syntax as the web runs list. Use `installation:` for installation runs and `version:` for the flow version active when the run started. ## Post-Release Targeting After `breyta flows release `, the live install is updated for the selected workspace by default. Track-latest end-user installations are also promoted unless you pass `--skip-promote-installations`. Default `breyta flows run ` still uses draft-target bindings/config. Use explicit flags when you want installation runtime behavior: | Target | Command | |---|---| | Draft setup/config (default) | `breyta flows run --wait` | | Installed live | `breyta flows run --target live --wait` | | Specific installation | `breyta flows run --installation-id --wait` | For new flows, declare per-run fields under `:invocations`, expose the author run button through `:interfaces :manual`, and pass values with `--input`. Older manual field shapes remain executable for compatibility, but new docs and new source should use `:invocations` plus `:interfaces :manual`. If you skipped end-user installation promotion during release (`--skip-promote-installations`), promote them later with: - `breyta flows promote ` ## Invocation Interfaces Use `:interfaces` when a solution flow should be callable by a manual run form, another program, HTTP client, webhook provider, or MCP/coding-agent tool. The source contract normally lives in `:invocations {:default ...}`; `:interfaces` only declares the call surface. A flow should normally expose one public invocation for the solution, with at most one manual adapter, one HTTP adapter, one webhook adapter, and one MCP adapter. Use extra invocation ids only when an adapter needs a genuinely different payload contract, not to turn one flow into several unrelated tools. Author smoke loop: ```bash breyta flows push --file ./flow.clj breyta flows run --input '{"key":"value"}' --wait breyta flows interfaces call --input '{"key":"value"}' --wait breyta flows release breyta flows interfaces list --target live breyta flows interfaces show --target live breyta flows interfaces curl --target live --input '{"key":"value"}' breyta flows interfaces call --target live --input '{"key":"value"}' --wait breyta flows metrics --source live ``` Consumer/installation loop: ```bash breyta flows installations create --name "Client API" breyta flows installations configure --input '{"setup":"value"}' breyta flows installations enable breyta flows installations interfaces breyta flows interfaces call --installation-id --input '{"key":"value"}' --wait breyta flows metrics --installation-id ``` Consumer composition recipe: 1. Discover or choose the public flow to reuse. 2. Create, configure, and enable an installation in the caller workspace. 3. Inspect `breyta flows installations interfaces `. 4. Use the installation-scoped HTTP or MCP interface from the wrapper flow, coding agent, or external client. Use installation-scoped endpoints for consumer calls: ```text /api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id} /api/workspaces/{workspace-id}/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id} ``` For MCP, connect to the installed Streamable HTTP interface with bearer auth, then call `initialize`, `tools/list`, and `tools/call`. Paid public flows can return entitlement errors such as `billing_trial_ended`; handle those as billing state rather than interface-shape failures. `flows interfaces curl` prints `Authorization: Bearer ${BREYTA_TOKEN}` as a placeholder. Interface calls still require normal workspace/API authentication in the current CLI path. For async or long-running interfaces, the HTTP response includes `data.statusUrl` when the run has a `workflowId`. That endpoint is scoped to the same interface surface and can be polled by API clients. The CLI `--wait` flag handles this polling for author smoke tests. Use `flows metrics` to inspect aggregate invocation health for invocation-backed client surfaces. Metrics are redacted counters and timings only; they do not include raw request bodies, response bodies, tokens, headers, secrets, or binding values. Use `--source draft` or `--source live` for author-owned interface calls, and `--installation-id ` for installed consumer calls. The returned `interfaceScope` keeps draft, live, and installation traffic separate even when they use the same interface id. Use `runs show ` when you need an individual run. MCP interfaces need the same explicit authorization boundary. A coding agent should connect to Breyta with a scoped workspace API credential for the installation/interface surface, and tool discovery should generally return one MCP tool for the installed solution flow. It should not expose non-interfaced or unauthorized invocations. Unlike normal HTTP interface calls, MCP `tools/call` requests wait for the backing flow run to complete up to a bounded 180-second timeout and return the flow result to the tool caller, with Breyta run metadata under `_breyta`. ## Run Targeting Matrix Use this matrix for `breyta flows run `. | Scenario | Required flags | Why | |---|---|---| | Normal run with draft-target bindings/config | none | Slug-only is the default path; executed code still resolves from the active version unless `--version` is set. | | Run installed live target (advanced) | `--target live` | Explicitly target installation runtime. | | Run a specific installation (advanced) | `--installation-id ` | Deterministic explicit target selection. | | Multiple matching installations | `--installation-id ` | Resolve ambiguity. | | Override flow release version for this run | `--version ` | Force a specific release version for this invocation. | | Provide invocation input payload | `--input '{...}'` | Pass per-run input to the flow. | | Wait until terminal status | `--wait` (optional `--timeout`, `--poll`) | Block and stream final run result. | ## Docs Lookup | Task | Command | |---|---| | Check installed skill drift | `breyta skills status --provider all` | | Search docs | `breyta docs find "flows run" --limit 5 --format json` | | Open page | `breyta docs show --section ` for focused reads; add `--full` only when needed | | Offline corpus grep | `breyta docs sync --out ./.breyta-docs --clean && rg -n "" ./.breyta-docs/pages` | ## Compatibility Notes Legacy command aliases remain executable for compatibility, but canonical guidance uses only the commands in this page. New manual, HTTP, webhook, and MCP ingress should be authored and inspected through interfaces. ## Related - [Start Here](/docs/start-here) - [Flow Grouping](/docs/guide-flow-grouping) - [Connections First](/docs/guide-connections-first) - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [CLI Commands](/docs/reference-cli-commands) - [Feedback And Reporting](/docs/guide-feedback-and-reporting) --- Document: Feedback And Reporting URL: https://flows.breyta.ai/docs/guide-feedback-and-reporting.md HTML: https://flows.breyta.ai/docs/guide-feedback-and-reporting Last updated: 2026-05-15T14:59:18+02:00 # Feedback And Reporting ## Quick Answer Use `breyta feedback send` to report product issues or feature requests from agent/human workflows. Reports go directly to the Breyta team and help prioritize platform bugs, confusing CLI/API behavior, missing docs, and lacking features we can address. Each report is stored for the Breyta team and can be broadcast to Slack. ## Default Usage Rule Send a report by default when one of these happens: 1) You hit a CLI/API failure and had to use a workaround. 2) You find missing capability that blocks or slows delivery. 3) Error text is unclear and causes repeated retries. 4) A run/release/configuration behavior looks wrong or inconsistent. ## Command ```bash breyta feedback send \ --type issue \ --title "flows run failed after release" \ --description "release succeeded but flows run returned target bindings missing" \ --flow daily-rollup \ --workflow-id wf-123 \ --run-id r45 \ --tag cli --tag bindings --agent ``` ## Flags | Flag | Type | Notes | |---|---|---| | `--type` | string | `issue`, `feature_request`, or `general` (default: `issue`) | | `--title` | string | required short summary | | `--description` | string | required detailed context | | `--source` | string | optional override: `agent`, `human`, `system` | | `--agent` | bool | marks report as agent-originated | | `--tag` | repeatable string | classification tags (repeat or comma-separated) | | `--command` | string | related command path/text | | `--flow` | string | related flow slug | | `--workflow-id` | string | related workflow id | | `--run-id` | string | related run id | | `--metadata` | JSON object | environment/tool metadata | | `--context` | JSON object | extra troubleshooting data | ## API Contract API mode command: - `command`: `feedback.send` - endpoint: `POST /api/commands` Example request body: ```json { "command": "feedback.send", "args": { "type": "issue", "title": "flows run failed after release", "description": "release succeeded but flows run returned target bindings missing", "source": "agent", "flowSlug": "daily-rollup", "workflowId": "wf-123", "runId": "r45", "tags": ["cli", "bindings"], "metadata": {"apiUrl": "http://localhost:8090"}, "context": {"stepId": "release"} } } ``` Example success response: ```json { "ok": true, "workspaceId": "ws-acme", "data": { "feedbackId": "abc123", "status": "submitted", "type": "issue", "source": "agent", "stored": true, "broadcast": {"slack": true} } } ``` ## Storage And Broadcast - Reports are stored durably for product triage. - A Slack markdown notification is sent for each submitted report. - To route feedback to a dedicated channel webhook, set `BREYTA_FEEDBACK_SLACK_URL`. - If `BREYTA_FEEDBACK_SLACK_URL` is unset, feedback uses `BREYTA_SLACK_URL`. - Slack delivery is best-effort; report storage still succeeds when Slack is unavailable. ## Safety - Do not include secrets, tokens, API keys, or raw PII. - Prefer concise reproduction steps and concrete IDs (`flow`, `workflowId`, `runId`). ## Related - [CLI Commands](/docs/reference-cli-commands) - [CLI Workflow](/docs/guide-cli-workflow) - [Troubleshooting](/docs/guide-troubleshooting) --- Document: Profiles URL: https://flows.breyta.ai/docs/guide-profiles.md HTML: https://flows.breyta.ai/docs/guide-profiles Last updated: 2026-02-17T13:14:35+01:00 # Profiles ## Quick Answer This is an advanced/internal model guide. Canonical end-user lifecycle language is `pull`, `push`, `validate`, and `run` (with `release`/`promote`/`installations` as advanced rollout operations). ## Why This Exists The backend runtime uses profile objects internally to store: - resolved bindings for `:requires` - activation/config inputs - enabled state and version pinning Most users should use installation commands instead of profile terminology. ## Canonical Mapping | Internal profile concept | Canonical user concept | |---|---| | Prod profile | Live installation target | | Installation profile | End-user installation target | | Draft target profile | Default run target | ## CLI Context vs Internal Profiles - CLI `--profile` selects CLI client config (API URL/token/workspace). - Internal profile ids are runtime identifiers used behind installation/run resolution. ## When You Need This Page Use this page only when debugging low-level behavior, migrations, or API internals that expose `profileId`. ## Related - [CLI Workflow](/docs/guide-cli-workflow) - [Installations](/docs/guide-installations) - [Core Concepts](/docs/reference-core-concepts) --- Document: Installations URL: https://flows.breyta.ai/docs/guide-installations.md HTML: https://flows.breyta.ai/docs/guide-installations Last updated: 2026-05-20T16:17:13+02:00 # 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: ```clojure {:discover {:public true} ...} ``` or after push: ```bash breyta flows discover update --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: 1. 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 --public=true` only after explicit approval. 2. Push the flow with `breyta flows push --file ...`. 3. If you chose the explicit path, run `breyta flows discover update --public=true` after push. 4. Release/promote it so there is an installable live version. 5. Verify the web Discover install dialog. For CLI indexing checks, use another workspace or `--include-own` only 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 --pretty` to confirm stored metadata includes `discover.public`. Use `breyta flows discover list` or `breyta flows discover search ` to browse the same public installable catalog the web app shows. This is different from `breyta flows search `, which searches actual workspace flow metadata, and from `breyta flows templates search `, which searches approved reusable templates to inspect and copy from. For paid public flows and Stripe seller onboarding, see [Paid Public Flows](/docs/guide-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: ```bash breyta flows installations create --name "Smoke install" breyta flows installations configure --input '{"":""}' breyta flows installations enable breyta flows run --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: ```bash breyta flows update --publish-description-file ./publish-description.md ``` or: ```bash breyta flows update --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: ```clojure {: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: ```bash breyta flows update \ --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: - `publishMedia` replaces the whole stored discover card media value - Use `--clear-publish-media` to remove it - For video, add `--publish-media-poster-kind` and `--publish-media-poster` for an optional poster - If you keep flow metadata in source control, you can also author the same value as `:publish-media` in 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: ```clojure {: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"}]} ``` ```clojure {: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-types` in authored worker requirements; `:job-type` is accepted as a single-type alias Rule of thumb: - put installer-owned config and setup fields in `:requires` - put external worker dependencies in `:requires` as `{: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. ```clojure {: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: ```clojure {: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: ```clojure {: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: ```clojure {: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: ```clojure {:requires [{:kind :form :collect :run :label "Open questions" :title :question :fields [{:key :question :label "Question" :field-type :text :required true}]}]} ``` Behavior: - `:label` changes the section heading from `Previous runs` / `Recent runs` to your chosen text. - `:title` changes 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 :resource` for new flows. - The run UI shows `Add resources` for existing workspace resources only when a resource input is declared. - Local upload is available on the run page as a separate `Upload file from computer` or `Upload files from computer` action when the field allows `:file` resources. `:file` is the default resource type. - Files uploaded from the run page are stored as normal workspace `:file` resources and auto-selected for the current run, so they also appear in later picker searches. - Use `:multiple true` when the flow expects a collection of resources. - Resource fields default to `:file`, which covers uploads and persisted blobs. - Use `:resource-types` and `:accept` only when you need to narrow what the user can select. - `:accept` supports 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-types` and `:accept` are 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: ```clojure {:invocations {:default {:label "Run input" :inputs [{:name :resources :label "Resources" :type :resource :required true :multiple true :accept ["application/pdf" "text/plain"]}]}}} ``` Text-focused example: ```clojure {: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: ```clojure {: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: ```clojure {: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: ```clojure {: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: ```clojure {: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): ```clojure {:invocations {:default {:inputs [{:name :question :type :text :label "Question" :required true}]}} :interfaces {:manual [{:id :ask :label "Ask" :invocation :default :enabled true}]}} ``` Example (webhook upload): ```clojure {: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: ```clojure {: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: ```bash 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: ```bash 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: ```bash breyta flows configure my-flow --schedule-reset weekly-review ``` Installers can update an installation schedule the same way: ```bash breyta flows installations configure \ --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: ```bash breyta flows installations configure --schedule-enable weekly-review breyta flows installations configure --schedule-disable weekly-review breyta flows installations configure --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 --name "My setup"` | | 2. Provide activation inputs from `:requires` form fields | `breyta flows installations configure --input '{"region":"EU"}'` | | 3. Enable the installation when setup is ready | `breyta flows installations enable ` | | 4. Inspect manual/HTTP/webhook/MCP invocation interfaces | `breyta flows installations interfaces ` | 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: ```bash breyta flows interfaces call \ --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: ```text /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: 1. Find the public flow in Discover or with `breyta flows discover search`. 2. Create, configure, and enable an installation. 3. Inspect callable surfaces with: ```bash breyta flows installations interfaces ``` 4. Smoke-test the installed HTTP interface: ```bash breyta flows interfaces call \ --installation-id \ --input '{"prompt":"A clean product image","quality":"low"}' \ --wait ``` 5. Use the installed endpoint from the wrapper flow, client, or agent: ```text /api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id} ``` The workspace-scoped alternate is also accepted: ```text /api/workspaces/{workspace-id}/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id} ``` HTTP clients should send the normal workspace/API authorization header: ```text Authorization: Bearer ``` 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: ```bash breyta flows metrics --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 ` | | 2. Upload one or more files to webhook interface | `breyta flows installations upload --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: ```bash breyta flows installations upload --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. ```clojure {: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 `:requires` with the right `:provided-by` - [ ] Memory table created at runtime by the flow, not hardcoded to a workspace URI - [ ] `:output-persist` used for any result the installer should access - [ ] `:cost` budget 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 --wait` before release ### Minimal installable agent flow example ```clojure {: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: ```clojure {: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: ```clojure {: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: ```bash breyta connections items breyta connections items --item-type github/repository --limit 25 breyta connections items --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. ## Related - [Start Here](/docs/start-here) - [Flow Authoring](/docs/build-flow-authoring) - [Step Agent](/docs/reference-step-agent) - [Packaged Steps](/docs/reference-packaged-steps) - [Profiles](/docs/guide-profiles) - [Flow Definition](/docs/reference-flow-definition) - [CLI Commands](/docs/reference-cli-commands) --- Document: Paid Public Flows URL: https://flows.breyta.ai/docs/guide-paid-public-flows.md HTML: https://flows.breyta.ai/docs/guide-paid-public-flows Last updated: 2026-05-21T15:31:38+02:00 # Paid Public Flows ## Quick Answer To sell a flow today: 1. Open the creator workspace billing page and complete seller payout onboarding. 2. Open the flow's Publish page. 3. Save the public listing, pricing or price catalog, and optional trial settings. 4. Publish the current draft as the live buyer version. 5. Verify buyer checkout from another workspace. Example: - Seller onboarding: `//billing` - Flow publishing: `//flows//publish` - Public app page: `https://breyta.ai/apps/` - Buyer verification: sign in as a different workspace and open `//discover` Breyta handles buyer checkout, Stripe webhooks, entitlement state, and payout routing. The creator does not create Stripe Checkout Sessions, products, or prices manually. The creator does need a seller account for their workspace so payouts can be routed correctly. Paid publishing and seller payout setup are currently behind the `BREYTA_FLOW_REUSE_APPROVER_USER_IDS` rollout gate. If the Publish page or seller-payout controls are missing, confirm that the current user id is included in that allow-list. ## What Breyta Handles Vs What The Creator Handles | Area | Breyta handles | Creator handles | |---|---|---| | Buyer payments | Stripe Checkout Sessions, webhook delivery, checkout completion, entitlement state | Nothing directly in Stripe Checkout | | Seller payouts | Connected-account routing, payout transfer metadata, Connect account persistence on the workspace | Completing Stripe onboarding and giving Stripe required payout details | | Flow publishing | Runtime install/check/run/release machinery | Authoring the flow, setting metadata, releasing the live version | ## Seller Onboarding (Once Per Workspace) Breyta uses Stripe Connect seller accounts for creator payouts. The current integration creates an Express connected account for the creator workspace and routes buyer payments to that account. Creator onboarding path: 1. Open `//billing`. 2. In the seller payouts card, start or resume Stripe onboarding. 3. Complete the Stripe-hosted onboarding flow. 4. Return to Breyta. The billing page refreshes seller payout state through `?billing_refresh=seller`. The workspace is ready to sell when the Connect state is effectively active. In sandbox validation, the ready state corresponded to: - `charges_enabled=true` - `payouts_enabled=true` - `card_payments=active` - `transfers=active` If seller onboarding is incomplete, buyer checkout is blocked with: - `Creator payouts are not configured for this flow` Operators can still use the admin Stripe Connect endpoints for diagnostics and sandbox runbooks, but they are no longer the normal creator path. ## Publish From The Flow Use the dedicated Publish page for the flow: ```text //flows//publish ``` The Publish page owns the creator-facing paid-flow controls: - public listing visibility for Discover/install surfaces - free, one-time, subscription, usage, or subscription usage pricing - optional price catalogs for multiple plans - subscription interval - before-install paid checkout - prepaid run-pack quantity for usage pricing - included runs for subscription usage pricing - optional subscription trial days - whether a trial requires a payment method - draft-to-live publishing - seller payout readiness - installs and checkout activity For a paid flow: 1. Make sure the flow has a saved draft. 2. Open the Publish page. 3. Choose `One-time purchase`, `Subscription`, `Usage per run`, or `Subscription usage`. 4. Enter amount and currency. 5. For subscriptions, choose the billing interval and optional trial settings. 6. For usage pricing, enter the run-pack quantity. For subscription usage, enter included runs per billing period. 7. Enable public listing. 8. Save settings. 9. Publish changes. Public paid flows can include manual interfaces, schedules, and webhook interfaces. End users manage automatic entrypoint enablement per installation. The checkout path charges one-time purchases, subscriptions, prepaid usage packs, and subscription-usage plans through Stripe Checkout. Usage packs and subscription usage then rely on Breyta's run ledger for allowance tracking. Trial-run packages can bypass Stripe first and start with a Breyta trial entitlement when the selected pricing shape supports that path. Advanced source/CLI workflows can still represent `paywall.when` explicitly. Breyta's default public-flow platform fee is 5% plus 30 cents USD. One-time USD checkout applies both components. Subscription checkout applies the percentage component through Stripe Checkout; fixed subscription fee settlement is tracked separately. The publish action creates the live version buyers install. If the draft differs from the live version, the page shows that the draft needs publishing. ## Author Monetization In Source (Advanced) The Publish page is the normal creator path. Source-first authoring remains useful for code-reviewed flow definitions, fixtures, and CLI-heavy operations. The flow file can carry marketplace monetization metadata directly, and `breyta flows push` persists it. For new paid apps, author the catalog under `:marketplace {:app {... :monetization {:plans [...]}}}`. Legacy flow-level `:marketplace {:monetization ...}` is still supported for existing listings, but new paid apps should use app-owned identity so Discover, checkout, billing, and install ownership all use the same app id and plan ids. ## Pricing Models Choose the pricing type based on what the buyer is purchasing: | Pricing type | Buyer pays | Buyer receives | Typical use case | Required fields | |---|---|---|---|---| | `:free` | Nothing | Free install and run access | Open tools, lead-gen flows, free templates | `pricing.type` | | `:one-time` | One upfront payment | Paid access without recurring billing | Fixed-fee utility, template, or app | `amount`, `currency` | | `:subscription` | Recurring monthly or yearly charge | Access while the subscription stays active | SaaS-style app access | `amount`, `currency`, `interval` | | `:usage` | One-time payment for a prepaid pack | A fixed number of successful runs with no time expiry | "$100 for 500 runs" | `amount`, `currency`, `unit`, `included-quantity` | | `:subscription-usage` | Recurring monthly or yearly charge | A fixed number of successful runs every billing period | "$49/mo includes 22 runs" | `amount`, `currency`, `interval`, `unit`, `included-quantity` | Operational rules: - `:usage` is prepaid packaging, not pay-as-you-go metering. - `:usage` packs do not expire by time; they are consumed only by successful completed runs. - `:subscription-usage` resets allowance on subscription renewal. - `:subscription-usage` does not currently bill paid overages. When included runs are exhausted, the buyer is blocked until renewal or a future product change. - Failed, cancelled, terminated, and timed-out runs do not consume paid allowance. ## Trial Options Supported trial shapes depend on the pricing type: | Pricing type | Supported trial | |---|---| | `:one-time` | `trial.runs` | | `:subscription` | `trial.days`, optional `trial.payment-method-required` | | `:usage` | `trial.runs` | | `:subscription-usage` | `trial.runs`, or `pricing.free-quantity` as the fallback no-card trial-run allowance when explicit `trial.runs` is absent | Important trial details: - `trial.days` is the subscription-style time-based trial. - `trial.runs` is the run-counted trial. - `pricing.free-quantity` on `:subscription-usage` is a PLG-style fallback trial entry point, not a second paid allowance bucket. - If both `trial.runs` and `pricing.free-quantity` are present on `:subscription-usage`, explicit `trial.runs` wins. Supported values: - `pricing.type`: `:free`, `:one-time`, `:subscription`, `:usage`, `:subscription-usage` - `pricing.amount`: positive price in major currency units. Decimals are allowed when the currency supports them, for example `"0.99"` USD. - `pricing.currency`: lowercase ISO currency code such as `"usd"` - `pricing.interval`: `:month` or `:year` for subscriptions and subscription usage - `pricing.unit`: `:run` for usage and subscription usage - `pricing.free-quantity`: zero or more free trial runs for `:usage`, or fallback no-card trial runs for `:subscription-usage` when `trial.runs` is not set - `pricing.included-quantity`: positive paid run quantity for `:usage`, or positive included runs per billing period for `:subscription-usage` - `paywall.when`: `:before-install` or `:before-run` for paid flows - `trial.days`: positive integer, optional - `trial.payment-method-required`: `true` or `false`, optional - `plans`: app-owned catalog entries under `:marketplace {:app {:monetization {:plans [...]}}}`. Every plan must normalize to a unique stable `plan-id`, every entry must be valid, and at most one plan can be marked `default` or `recommended`. If no default is set, the first plan is treated as the default. - Seat-based pricing is not implemented. Do not describe a plan as "N seats" or "N installs" unless the product has added explicit seat entitlements. Use run quantities only for usage-priced plans. New app-owned source example: ```clojure {:slug :customer-insights :name "Customer Insights" :tags ["analytics" "customer-insights"] :discover {:public true} :marketplace {:visible true :app {:app-id "customer-insights-suite" :app-name "Customer Insights" :app-primary-flow-slug "customer-insights" :app-flow-slugs ["customer-insights"] :monetization {:plans [{:plan-id "starter" :name "Starter" :default true :pricing {:type "subscription" :amount 29 :currency "usd" :interval "month"} :trial {:days 7 :payment-method-required false}} {:plan-id "pro-pack" :name "Pro Pack" :pricing {:type "usage" :amount 49 :currency "usd" :unit "run" :included-quantity 250}} {:plan-id "metered" :name "Metered" :pricing {:type "subscription-usage" :amount 99 :currency "usd" :interval "month" :unit "run" :included-quantity 1000}}]}}}} ``` Use a single plan in the catalog for a one-price app. Existing legacy flow-level examples with `:marketplace {:monetization {:pricing ...}}` remain valid for preserving old listings. ## Multi-Plan Catalogs Use a plan catalog when a buyer should choose among multiple pricing options for the same app, for example: - monthly vs annual subscription - starter vs pro tiers - subscription plus prepaid run-pack options - a recommended default plan with alternatives Rules: - each plan must normalize to a unique stable `plan-id` - at most one plan can be marked `default` or `recommended` - existing buyer entitlements store the selected `plan-id`, so deleting or renaming active plan ids is risky and should be treated like a migration Example: ```clojure {:slug :paid-flow-catalog :tags ["catalog"] :discover {:public true} :marketplace {:visible true :app {:app-id "paid-flow-catalog" :app-name "Paid Flow Catalog" :app-primary-flow-slug "paid-flow-catalog" :app-flow-slugs ["paid-flow-catalog"] :monetization {:plans [{:plan-id "starter" :name "Starter" :default true :pricing {:type "subscription" :amount 29 :currency "usd" :interval "month"}} {:plan-id "pro-pack" :name "Pro Pack" :pricing {:type "usage" :amount 100 :currency "usd" :unit "run" :included-quantity 500}} {:plan-id "metered" :name "Metered" :pricing {:type "subscription-usage" :amount 49 :currency "usd" :interval "month" :unit "run" :included-quantity 22 :free-quantity 5}}]}}} ...} ``` If you operate directly against `/api/commands`, the underlying monetization command is `flows.marketplace.monetization.update`. For source-authored paid apps, prefer the app-owned catalog shape above. For day-to-day creator work, prefer the Publish page so public listing, monetization, and draft publishing stay in one place. ## Publish Checklist App path: 1. Confirm seller payouts are ready on `//billing`. 2. Open `//flows//publish`. 3. Save the public listing and paid-flow settings. 4. Publish changes so the current draft becomes the live buyer version. 5. Verify from another workspace. Source/CLI path: 1. Author the flow with descriptive metadata tags for the use case, audience, integration, and modality. Do not treat `end-user` as an installability requirement; existing legacy flows may still carry it as ordinary metadata. 2. For a new paid app, add `:marketplace {:app {... :monetization {:plans [...]}}}` for paid behavior. Keep `:marketplace {:monetization ...}` only when preserving an existing legacy listing. 3. Choose one marketplace visibility path: - source-first: add `:marketplace {:visible true}` in the flow file before push - explicit after push: run `breyta flows marketplace update --visible=true` 4. Choose one discover visibility path: - source-first: add `:discover {:public true}` in the flow file before push - explicit after push: run `breyta flows discover update --public=true` 5. Push the flow: ```bash breyta flows push --file ./tmp/flows/.clj ``` 6. Run the explicit visibility commands if you chose the explicit path. 7. Release a live version: ```bash breyta flows release --release-note-file ./release-note.md ``` 8. Verify the buyer surface from another workspace. ## Verify The Published Result From the Publish page, check that: - seller payouts are ready for paid flows - availability is public - pricing and trial settings match the intended buyer offer - paid checkout is before install - the live version is up to date From the CLI, use: ```bash breyta flows show --pretty breyta flows discover list breyta flows discover search breyta flows readiness --target live --public --marketplace ``` Check for: - `tags` are descriptive metadata; installability comes from public visibility, marketplace visibility, and a released live version, not from an `end-user` tag. - `marketplace.visible` is `true` - `marketplace.app.monetization.plans` contains the intended pricing, paywall, and optional trial fields for new paid apps. Existing legacy listings may still report `marketplace.monetization`. - `discover.public` is `true` - the flow has an active released version - the public app page at `https://breyta.ai/apps/` opens the intended buyer-facing app surface Then verify the actual buyer experience from another workspace in the web app: - the flow appears in `/:workspace-id/discover` - the CTA matches one-time vs subscription vs trial behavior - checkout completes - the resulting installation state matches the paywall policy ## Current Product State The current publishing flow is real and creator self-serve. Today: - seller onboarding starts from the workspace billing page - paid-flow publishing starts from the flow Publish page - the Publish page writes listing, pricing, trial, and live publish state - CLI/source metadata still works for scripted or code-reviewed publishing - paid publishing is gated by `BREYTA_FLOW_REUSE_APPROVER_USER_IDS` during rollout Treat the app path as canonical for normal creator use, and the CLI/source path as an advanced/operator path. ## Related - [CLI Workflow](/docs/guide-cli-workflow) - [CLI Commands](/docs/reference-cli-commands) - [Flow Metadata And Discover](/docs/reference-flow-metadata) - [Installations](/docs/guide-installations) --- Document: Secrets URL: https://flows.breyta.ai/docs/guide-secrets.md HTML: https://flows.breyta.ai/docs/guide-secrets Last updated: 2026-05-15T14:59:18+02:00 # Secrets ## Quick Answer Use this guide to declare secret slots, bind secret values, rotate credentials, and wire secret refs into interfaces and steps. This guide covers secret slots, secret refs, bindings, rotation, and how secrets are consumed by webhook interfaces and steps. ### Core concepts | Concept | Meaning | |---|---| | Secret ref | Secret value stored under a `:secret-ref` key. | | Secret slot | Declared in `:requires` with `:type :secret`. | | Binding contract | Bindings map slot to `:secret-ref`; secret value is stored separately. | | Source control rule | Flow definitions must never embed secret values. | ### Deploy-key guard secret (release control) Deploy-key guard uses a separate server-side secret for release protection. | Aspect | Value | |---|---| | What it protects | Guarded flow release/promotion paths (`flows release`, `flows promote`, and disabling `:deploy-key-required`). | | Where it is defined | Breyta environment secret `BREYTA_FLOW_DEPLOY_KEY` (configured by platform/admin). | | How CLI provides it | `--deploy-key ""` or local/CI env `BREYTA_FLOW_DEPLOY_KEY` when running release commands. | | Storage model | Global Breyta deployment secret, not a flow `:requires` slot, bindings secret, or connection. | | Flow opt-in switch | `:deploy-key-required true` in flow definition metadata. | Notes: - Enabling `:deploy-key-required true` fails unless `BREYTA_FLOW_DEPLOY_KEY` is configured for the Breyta environment. - Keep this secret in CI/runtime secret manager and inject it per job/session. ### Declare a secret slot Always set an explicit `:secret-ref` on secret slots. ```clojure {:requires [{:slot :webhook-secret :type :secret :secret-ref :webhook-secret :label "Webhook Secret"}]} ``` ### Provide a secret value (configure) For human-entered secrets, prefer Breyta setup/connection UI secret fields and do not ask for secrets in chat. Protected setup links should return through login. For automation or explicit CLI setup, set the secret on the draft target: ```bash breyta flows configure --set webhook-secret.secret="YOUR_SECRET_VALUE" ``` ### Generate a new secret value Use `:generate` to create a secret value server-side: ```edn {:bindings {:webhook-secret {:secret :generate}}} ``` ## Secret Lifecycle (CLI) | Stage | Command / action | Result | |---|---|---| | Human setup | Enter the value in the setup/connection UI secret field | Stores the secret without chat/source exposure | | Bind value | `breyta flows configure --set .secret="VALUE"` | Stores secret under slot `:secret-ref`. | | Generate value | `breyta flows configure --set .secret=:generate` | Server generates and stores secret value. | | Rotate | Re-run `flows configure` with updated secret value | Replaces value under same `:secret-ref`. | After applying generated secrets, inspect the target profile/bindings metadata. If a live profile still reports no secret bindings, configure the slot explicitly: ```bash breyta flows configure --target live --version latest --set webhook-secret.secret=:generate ``` Re-run `breyta flows configure check --target live --version latest` and a live-shaped smoke test before trusting webhook auth or secret-backed steps. ### Rotate a secret 1) Apply the new secret value with `flows configure`. 2) Re-run to store the new value under the same `:secret-ref`. 3) Update external systems to use the new secret. ```bash breyta flows configure --set webhook-secret.secret="NEW_SECRET_VALUE" ``` ### Using secrets in webhook auth Auth configs reference secrets via `:secret-ref`: ```clojure {:auth {:type :api-key :header "X-API-Key" :secret-ref :webhook-secret}} ``` ### Service account JSON secrets (Google APIs) Some integrations need a full JSON service account key (not a single token string). Store the **entire JSON payload** as a secret value and reference it via `:secret-ref`. Declare the slot: ```clojure {:requires [{:slot :google-drive-service-account :type :secret :secret-ref :google-drive-service-account :label "Google Drive service account JSON"}]} ``` Bind the value (prod): ```edn {:bindings {:google-drive-service-account {:secret ""}}} ``` Use it in an HTTP step auth block: ```clojure {:auth {:type :google-service-account :secret-ref :google-drive-service-account :scopes ["https://www.googleapis.com/auth/drive.readonly" "https://www.googleapis.com/auth/drive.metadata.readonly"]}} ``` ```clojure {:auth {:type :hmac-sha256 :header "X-Signature" :secret-ref :webhook-secret}} ``` ```clojure {:auth {:type :basic :username "webhook-user" :secret-ref :webhook-basic-password}} ``` ### Common mistakes | Mistake | Consequence | |---|---| | Omitting `:secret-ref` on secret slot | Validation/runtime binding failures. | | Putting secret values in flow definitions | Secret leakage risk in source and review surfaces. | | Mismatching interface auth `:secret-ref` and slot `:secret-ref` | Auth checks fail at runtime. | | Assuming generated secrets were stored without inspecting target bindings | Live webhook auth or secret-backed steps can fail at runtime. | ## Related - [Start Here](/docs/start-here) - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [CLI Commands](/docs/reference-cli-commands) --- Document: Google Drive Sync URL: https://flows.breyta.ai/docs/guide-google-drive-sync.md HTML: https://flows.breyta.ai/docs/guide-google-drive-sync Last updated: 2026-05-15T14:59:18+02:00 # Google Drive Sync (Service Account) ## Quick Answer Use this guide to run and operate the Google Drive service-account sync flow, including configuration, schedule behavior, and verification. This guide covers a production flow that continuously syncs files from a Google Drive folder into Breyta blob storage and writes per-file metadata records for querying. ## Why service account auth (not user OAuth) Google OAuth user auth is poor for unattended syncing because it can require re-authentication. Use a **Google service account** (SA) so scheduled runs can mint access tokens without user action. ## Flow + schedule | Item | Value | |---|---| | Flow slug | `google-drive-folder-sync` | | Trigger | Schedule every 2 hours (`cron: 0 */2 * * *`, `timezone: UTC`) | | Folder input | Drive folder URL or raw folder id | | Archive slot | `:archive` (`:blob-storage`) | ## Required secret The flow uses `:auth {:type :google-service-account ...}` on `:http` steps. | Secret setting | Value | |---|---| | Secret slot / ref | `google-drive-service-account` | | Secret value | Full JSON service account key payload | Configure it on the draft target: | Step | Action | |---|---| | 1 | Save service account JSON to `./secrets/google-drive-sa.json` (local only; do not commit). | | 2 | `breyta flows configure google-drive-folder-sync --set google-drive-service-account.secret=@./secrets/google-drive-sa.json` | | 3 | `breyta flows run google-drive-folder-sync --input '{"folder":"https://drive.google.com/drive/folders/?usp=sharing","cursor_key":"google-drive-sync:cursor"}' --wait --pretty` | ## Required storage setup The flow also declares a blob-storage slot `:archive`. That slot always requires the installer to choose a storage root. Recommended setup values: | Setting | Example | |---|---| | Connection binding | `platform` | | Storage root | `gdrive` | If you change the storage root, persisted files and runtime resource pickers both move to that new storage location automatically. ## Run now (manual) ### Incremental run (uses the shared cursor) This is the safest way to “run now”, because it uses the same cursor the schedule uses. ```bash breyta flows run \ google-drive-folder-sync \ --input '{"folder":"https://drive.google.com/drive/folders/?usp=sharing","cursor_key":"google-drive-sync:cursor"}' \ --wait --pretty ``` ### Isolated/forced scan (separate cursor) Use a unique `cursor_key` to force a “fresh” scan without disturbing the schedule cursor. ```bash breyta flows run \ google-drive-folder-sync \ --input '{"folder":"https://drive.google.com/drive/folders/?usp=sharing","cursor_key":"google-drive-sync:cursor:manual-check:20260101T000000Z"}' \ --wait --pretty ``` Notes: | Mode | When to use | Cursor behavior | |---|---|---| | Incremental run | Safe manual run aligned with schedule state | Uses shared cursor (`google-drive-sync:cursor`) | | Isolated/forced scan | One-off fresh scan without affecting schedule cursor | Uses unique `cursor_key` | | Note | Guidance | |---|---| | Singleton flow run ids | Repeated manual starts may reuse stable workflow id; verify by timestamps in `runs list`/`runs show`. | | `--installation-id` on manual runs | Avoid unless you explicitly need a specific installation target KV namespace. | ## What gets written ### Blobs Downloads are persisted through the `:archive` slot. The slot contributes the configured storage root, and the step contributes the folder id: - configured root: `gdrive` by default - `:path ""` - `:filename "."` Resulting runtime path shape: ```text workspaces//storage///. ``` Google Docs/Sheets are exported to: | Source type | Export type | |---|---| | Docs | `text/plain` (`.txt`) | | Sheets | `xlsx` | | Other Google-apps types | `pdf` | ### Per-file metadata records For each downloaded file the flow writes a KV doc: | Item | Shape | |---|---| | Key format | `google-drive-sync:meta::` | | Value fields | `:folder-id`, `:file-id`, `:blob-filename`, plus Drive fields like `:name`, `:mimeType`, `:modifiedTime`, `:size`, `:driveId` | ## How to verify a run After a run completes: | Check | Expected signal | |---|---| | `download-files` step | `successes` / `failures` counts match expectations. | | `meta-` KV writes | Entries show `{:success true, :key "...", :created? ...}`. | | Cursor write step (`set-cursor`) | Updates `google-drive-sync:cursor` (or your custom cursor key). | ## Related - [Start Here](/docs/start-here) - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [CLI Commands](/docs/reference-cli-commands) --- Document: n8n Import URL: https://flows.breyta.ai/docs/guide-n8n-import.md HTML: https://flows.breyta.ai/docs/guide-n8n-import Last updated: 2026-05-20T03:00:25+02:00 # n8n Import (CLI Template) ## Quick Answer Use this guide to import n8n workflows into Breyta with runnable step mappings, fallback rules, and migration-safe patterns. Purpose: import an n8n workflow JSON into a **runnable Breyta flow ASAP** with best-effort translations. Keep all prompts, code, request bodies, and messages **like-for-like** in the output. This reference is written for coding agents that generate the EDN flow file. ## Import loop (plan-first) 1) Parse n8n JSON and list nodes, connections, credentials, and expressions. 2) Map triggers and nodes to Breyta equivalents (tables below). 3) Mark unsupported/custom nodes and choose a runnable fallback. 4) Generate `:requires` slots from credentials (strip keys/secrets). 5) Emit `:templates` / `:functions` and the `:flow` orchestration. 6) Configure-check and run (optionally validate for explicit read-only checks). **Do not review or critique the workflow design.** The importer's job is translation, not improvement. If something is missing or unclear, add a `TODO(n8n-import)` note instead of suggesting optimizations. Do not add “conversion notes” that propose changes (e.g., persistence, schedules, model tweaks) unless asked. ## Import loop (step-by-step, strict) Use this when reliability matters more than speed 1) Translate exactly one node into a Breyta step 2) Run the step in isolation with a small input and inspect the output shape 3) Fix shape and size issues before translating the next node 4) Only then move on to the next node in the graph This avoids mismatched data shapes and large payload failures later in the flow ## Persist-first defaults Assume many HTTP steps will exceed the inline 512 KB limit - Add `:persist {:type :blob}` to HTTP steps unless you can confirm the payload is small - `:persist` removes the inline `:body`. Downstream steps must read the blob or use `:body-from-ref` or `:from-ref` - If a downstream function expects inline JSON, add a read step that restores the same shape as the original HTTP response ## Reliability notes from recent imports - Workspace-target config and installation-target config are distinct. Verify you configured the same target you are running. - Move URL query params into `:query` so they are not lost when you split base URL and path - Persisted HTTP responses do not include inline `:body`. Add a read step or use `:body-from-ref` - Align step output keys to downstream expectations. If a step outputs `:html`, but the next step expects `:content`, add an alias - Slow HTTP endpoints can time out even when they complete. Prefer `:timeout` on the HTTP step when a service is known to take longer - For long LLM calls, wrap them in `flow/poll` and set a `:return-on` clause that checks `:content` ## Naming rules - Step id: n8n node `name` -> kebab-case keyword (no `n8n-` prefix). - Normalize: lower-case, then replace non `[a-z0-9-]` with `-`, collapse repeats, trim `-`. - If the name is empty or starts with a non-letter, prefix with `step-`. - De-dupe: append `-2`, `-3`, etc for collisions. - Template ids: `:-request`, `:-prompt`, `:-sql`. - Function ids: `:-fn`. - Preserve the original n8n node `name` in a `;;` comment above the step. ## Keep content like-for-like - Prompts/messages/HTTP bodies: copy verbatim into `:templates` (preferred) or `:json`/`:body`. - n8n expressions (`{{$json...}}`, `{{$node...}}`) do **not** run in Breyta. - Translate expressions into a `:function` step that computes the needed values. - Preserve the original expression in a comment block when translation is non-trivial. - The CLI importer translates a conservative subset automatically: - `$json` paths, including bracket paths such as `{{$json["query"]["first_name"]}}`. - Simple binary `+`, ternaries, and post-increment expressions. - `$node["Name"].json...` references only when the referenced node has already been bound. Forward references stay as TODOs. - HTTP URL/query templates with simple `$json`/safe `$node` paths become Breyta Handlebars variables in the flow-local HTTP template. - Prefer a **per-node “prep” function** that computes all expression-derived fields, then pass its output into the step/template. - Code nodes: attempt a real translation to a Breyta `:function` (Clojure) so the flow runs as intended. - Only fall back to `(fn [input] input)` when translation is unclear; keep the original code verbatim in comments. - Java interop in `:function` steps is restricted (small allowlist). Prefer `breyta.sandbox` helpers. For time/date, you can use allowlisted `java.time.*` for parsing/formatting, but avoid deriving "now" in code; pass timestamps/flags via inputs (e.g. `:weekday`) or use runtime-provided time utilities, and add a TODO if missing. Example comment block: ```clojure ;; TODO(n8n-import): port JS code below to Clojure ;; --- begin n8n code --- ;; ;; --- end n8n code --- ``` Example expression translation note: ```clojure ;; TODO(n8n-import): translated n8n expression {{$json.user.id}} to (get-in input [:user :id]) ``` ### Expression translation quick map (best-effort) Use a `:function` step to compute these values: - `{{$json}}` -> `input` - `{{$json.foo}}` -> `(get input :foo)` / `(get-in input [:foo])` - `{{$json.foo.bar}}` -> `(get-in input [:foo :bar])` - `{{$node[\"Node Name\"].json}}` -> `` - `{{$node[\"Node Name\"].json.foo}}` -> `(get-in [:foo])` - `{{$node[\"Node Name\"].json[\"page\"]++}}` -> `(inc (or (get-in [:node-name :page]) 0))` when the node is available. - `{{$json.foo + 1}}` -> `(+ (get input :foo) 1)` - `{{$json.foo ? \"yes\" : \"no\"}}` -> `^{:label \"Condition\" :yes \"true\" :no \"false\"} (if (get input :foo) \"yes\" \"no\")` - `{{$json.a + $json.b}}` -> `(+ (get input :a) (get input :b))` - `{{$now}}` -> `(flow/now-ms)` (confirm time semantics) - `{{$today}}` -> derive from `(flow/now-ms)` (TODO if date formatting is needed) If an expression uses n8n-only helpers (e.g., `$items()`, `$runIndex`, `$prevNode`), add a TODO and compute it explicitly in a function. ## Items vs maps (n8n items -> Breyta data) n8n nodes operate on **arrays of items**; Breyta steps receive a **map**. Import rule: - Wrap n8n items as `{:items [...]}` in Breyta. - Use `:function` steps to map/filter/reduce items explicitly (data transformation). - Default for side‑effects: iterate per item with `for` unless the n8n node is explicitly “first item only”. Example: ```clojure {:items [{:id 1} {:id 2}]} ``` ```clojure (flow/step :function :map-items {:ref :map-items-fn :input {:items items}}) ``` Example (side‑effects per item): ```clojure (let [items (:items input) results ^{:label "Process each item"} (for [item items] (flow/step :http :post-item {:connection :api :path "/items" :method :post :json item}))] {:results (vec results)}) ``` ## Common migration pitfalls (watch for these) - Query params: move URL query strings into `:query` so the runtime includes them - Auth placement: keep header vs query as in n8n, do not guess - Lazy seqs: wrap `for` output with `vec` before passing into `:function` steps - Java interop: restricted allowlist; prefer `breyta.sandbox` helpers; for time, use `flow/now-ms` + explicit inputs - Regex: do not use `\\s`, use explicit character classes - Persisted HTTP: inline `:body` is omitted, add a read step or `:body-from-ref` if downstream expects JSON Split In Batches: - Use `(partition-all n items)` in a `:function` step, then `for` over batches. ## Graph translation (multi-input / branching) Convert the n8n graph into a topologically ordered `let` form: 1) Build a DAG from connections. 2) Order nodes so inputs are bound before use. 3) For multi-input nodes, pass a map of upstream results. Example multi-input: ```clojure (let [a (flow/step :http :fetch-a {...}) b (flow/step :http :fetch-b {...}) merged (flow/step :function :merge {:ref :merge-fn :input {:left a :right b}})] merged) ``` Branching: ```clojure ^{:label "Branch decision" :yes "True branch" :no "False branch"} (if condition (flow/step :http :true-branch {...}) (flow/step :http :false-branch {...})) ``` Loops / Split in Batches: - Use `for` for orchestration over items/batches. - Use `:function` to build batches (`partition-all`) and to reduce results. - For cyclic n8n graphs, only reference nodes that have already been safely bound in the emitted `let`. Pass those references explicitly as an input map, for example `{:set set :increment increment}`. - Do not synthesize a forward reference to a later node. If the loop requires true repeated execution, keep a single-pass fallback and add a TODO. - If you cannot preserve the loop semantics safely, add a TODO and keep a single-pass fallback. ## Trigger mapping | n8n trigger | Breyta surface | Notes | | --- | --- | --- | | Manual Trigger | `:interfaces {:manual [...]}` | Always include one manual interface for run-now testing. | | Webhook Trigger | `:interfaces {:webhook [...]}` | See `GUIDE_WEBHOOKS_AND_SECRET_REFS.md`. Requires auth + secret slot. Legacy webhook event triggers are accepted only for compatibility. | | Cron / Schedule | `:schedules` | Map cron/timezone if present; else TODO. | | Interval / Polling | `:schedules` | Convert to cron if possible; otherwise TODO. | | Service triggers (Slack, GitHub, etc.) | `:interfaces :webhook` or `:schedules` | If the service supports webhooks, default to webhook interface + TODO. | Minimal webhook interface + secret: ```clojure :requires [{:slot :webhook-secret :type :secret :secret-ref :webhook-secret :label "Webhook Secret"}] :invocations {:default {:inputs []}} :interfaces {:webhook [{:id :inbound-webhook :invocation :default :event-name "inbound.webhook" :auth {:type :api-key :secret-ref :webhook-secret}}]} ``` ## Step mapping (best-effort) | n8n node | Breyta step | How to translate | | --- | --- | --- | | HTTP Request | `:http` | Prefer `:template` with `:request` (URL -> base-url + path). | | Webhook Response | `:function` | Return a response map: `{:status 200 :headers {} :body ...}`. See `GUIDE_WEBHOOKS_AND_SECRET_REFS.md#webhook-response-maps`. | | Set | `:function` | Map/merge fields into a new map. | | Item Lists | `:function` | For split-out fields, normalize the selected field to `:items` and `:-items`. Verify exact batching semantics. | | HTML Extract | `:function` | Simple id selectors (for example `#firstHeading`) can be extracted from `:body`/`:html`; complex selectors remain TODOs. | | Code (JS/Python) | `:function` | Translate to Clojure; only use placeholder + TODO when unclear. | | IF / Switch | flow `if`/`cond` + `:function` | If expression maps cleanly, use it; else TODO and default to a **safe** false branch (or pass-through) to avoid unintended side effects. | | Merge | `:function` | `(merge a b)` or custom merge logic. | | Wait / Delay | `:wait` | Use `:timeout` or placeholder with TODO. | | Database (Postgres/MySQL/etc.) | `:db` | Put SQL in `:template` and pass `:params`. | | LLM / OpenAI / AI nodes | `:llm` | Convert prompt/messages to `:template` and map inputs. | | NoOp / Start | `:function` | Identity function. | ### HTTP Request translation If the n8n node has a full URL, split it: - `:base-url` = scheme + host - `:path` = path + query (or use `:query` map) - For expression URLs, translate simple n8n expressions to Handlebars template variables and pass the resolved node/input map as `:data`. - Example: `=https://api.github.com/users/{{$node["Set"].json["githubUser"]}}/starred` becomes path `/users/{{set.githubuser}}/starred` with `:data {:set set}`. - Query expressions follow the same rule: `={{$node["Set"].json["page"]}}` becomes `"{{set.page}}"`. Example: ```clojure :requires [{:slot :api :type :http-api :label "Imported API" :base-url "https://api.example.com" :auth {:type :api-key}}] :templates [{:id :fetch-user-request :type :http-request :request {:path "/users/{{id}}" :method :get :headers {"Accept" "application/json"}}}] (flow/step :http :fetch-user {:connection :api :template :fetch-user-request :data {:id user-id}}) ``` ## Credentials -> :requires (strip secrets) n8n exports often include credential *values*. Do **not** copy secrets. Rules: - For each unique credential reference, create a `:requires` slot. - Use `:type` based on node family: - HTTP nodes -> `:http-api` - DB nodes -> `:database` - LLM nodes -> LLM-capable `:http-api` connections - Unknown/custom -> `:secret` - Include `:label` derived from credential name or node name. - Leave auth type as best-guess (often `:api-key`), but **never** include keys. - If `:base-url` cannot be derived, set a placeholder and add TODO to fill. - Do not second‑guess auth placement (header vs query) unless the n8n node explicitly specifies it; copy the n8n config as-is and add a TODO if unclear. Example: ```clojure :requires [{:slot :stripe :type :http-api :label "Stripe API" :base-url "https://api.stripe.com" :auth {:type :bearer}}] ``` ## Unsupported or custom nodes Always emit a runnable fallback step and add a TODO: - Default fallback: choose based on intent. - If the node performs an API call or has a URL/credentials -> `:http`. - Otherwise -> `:function`. - Add `;; TODO(n8n-import):` comment describing what to implement. - **Add a web-search note**: “Search the web for API docs to rebuild this node as HTTP.” Example: ```clojure ;; TODO(n8n-import): Custom node "Acme CRM" not supported. ;; TODO(n8n-import): Search the web for Acme CRM API docs and rebuild as HTTP. (acme (flow/step :http :acme {:url "https://api.acme.com" ;; placeholder :method :post :json {}})) ``` ## Runnable ASAP defaults - Always include a manual interface, even if the n8n flow is only webhook/schedule. - If a step cannot be translated, emit a placeholder `:function` that returns input. - Keep the flow deterministic; avoid non-deterministic side effects in placeholder logic. - Only return **response maps** for webhook-interface flows; otherwise return normal flow output. - Do not add “review” commentary about missing steps or improvements; only translate and note TODOs. ## CLI import workflow (Breyta‑style) Follow the Breyta CLI loop so the imported flow is testable fast: 1) Generate the first-pass EDN file with `breyta flows import n8n workflow.json --slug ` - Default output: `./tmp/flows/.clj` - Use `--out ./tmp/flows/.clj` to choose a path explicitly - Use `--server-validate` when an API target is configured and you want the importer to push the generated draft and run `flows validate` immediately. 2) Review in-file `TODO(n8n-import)` notes and complete unsupported/custom semantics. 3) If you did not use `--server-validate`, run `breyta flows push --file ./tmp/flows/.clj` (this updates draft target). 4) Configure required slots/inputs (no secrets in repo), for example `breyta flows configure --set .conn=conn-...` 5) `breyta flows configure check ` 6) Optional read-only verification after later edits: `breyta flows validate ` 7) `breyta flows run --input '{}' --wait` Server validation is opt-in because it mutates the workspace draft. It requires `--api`/`BREYTA_API_URL`, workspace, and auth flags/env to point at the intended workspace. A successful response includes `data.serverValidation.pushedDraft: true` and `data.serverValidation.valid: true`. If you build a converter script: - Prefer adding it to `breyta-cli` so agents can call it directly, or use a real file over large inline heredocs. - Run `python -m py_compile` before executing. - Avoid f-strings that contain backslashes inside `{...}`; precompute strings or use `.format`. Notes: - `flows push` updates the draft working copy and draft-target bindings/config. - `flows import n8n --server-validate` also updates the draft working copy, then validates `source=draft`. - `flows run` still needs an active version by default, so brand-new flows must be deployed or released before that command will run them. - `flows release` is an advanced rollout step for live-install behavior. - A real public-template fixture currently validates server-side with only review TODOs left for sticky notes and unresolved IF condition semantics after supported data-transform and safe back-reference translations. If `breyta` is not on PATH: - Ask the user for the correct CLI path or to add it to PATH, then re-run the same commands. Regex conversion guardrails: - Do not inject literal newlines into regex literals (e.g., `#"\r?\n"` must keep `\\n`). - Prefer `re-pattern` with a normal string when in doubt: `(re-pattern \"^...$\")`. - Avoid unsupported escapes (e.g., `\\s`). Use character classes instead: `[\\t\\n\\r ]` or `\\p{Space}` if supported. - Do not propose using `\\s` as a fix; it remains unsupported. Replace with explicit character classes. - If a push fails with a regex parse error, fix the pattern and re-run; do not speculate in output. - When reporting fixes, state only the concrete change (e.g., “replaced \\s with [\\t\\n\\r ]”), not guesses. - Do not switch algorithms (e.g., replace regex with substring logic) unless the user asks; add a TODO instead. - Avoid double-escaping braces/backslashes in regex literals; prefer `#\"\\{.*\\}\"` (single escaping) or `re-pattern` with a normal string. - Do not run blanket regex-escape rewrites across the flow. Fix only the specific pattern that failed and only if you can describe the exact change. - Do not invent root-cause speculation (e.g., “metadata parsing issues”) without evidence. If parsing fails, report the error and inspect the exact offending literal. - Do not run unrelated shell experiments (e.g., `clojure -e` probes) unless the user explicitly asks. ## Output expectations - Output only the translated flow file path and a short TODO list derived from in‑file `TODO(n8n-import)` notes. - Default behavior: after import, **push the flow**, configure it, and run `flows configure check` unless the user explicitly opts out. - If the user is **new** or asks for next steps, suggest the canonical workflow only (push → configure → configure check → run). Treat release/promote/installations as advanced. ## Validation checklist Use the CLI import workflow above; if you need a quick list, it’s the same steps. - Local importer validation checks delimiter balance, EDN readability, required top-level keys, quoted `:flow`, and supported invocation input types. - Server validation (`--server-validate` or `flows push` followed by `flows validate`) checks the generated source against the real Breyta flow compiler/validator. - Server validation does not prove every n8n semantic is identical. Remaining TODOs still need human/agent review before release, especially loops, custom nodes, complex selectors, and condition semantics that were defaulted safely. ## Minimal example (n8n HTTP + Code) **n8n**: - HTTP Request -> GET https://api.example.com/users - Code -> transform users **Breyta** (sketch): ```clojure {:slug :imported :name "Imported Flow" :concurrency {:type :singleton :on-new-version :supersede} :requires [{:slot :api :type :http-api :label "Imported API" :base-url "https://api.example.com" :auth {:type :api-key}}] :templates [{:id :get-users-request :type :http-request :request {:path "/users" :method :get}}] :functions [{:id :transform-users-fn :language :clojure :code "(fn [input] input)"}] :invocations {:default {:inputs []}} :interfaces {:manual [{:id :run :label "Run" :invocation :default :enabled true}]} :flow '(let [input (flow/input) request ^{:label "Request source" :yes "Use input override" :no "Use imported template"} (if (:request input) (:request input) {}) users (flow/step :http :get-users {:connection :api :template :get-users-request :data request}) ;; TODO(n8n-import): port JS code below to Clojure ;; --- begin n8n code --- ;; ;; --- end n8n code --- result (flow/step :function :transform-users {:ref :transform-users-fn :input {:users users :input input}})] result)} ``` ## Related - [Start Here](/docs/start-here) - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [CLI Commands](/docs/reference-cli-commands) --- Document: Output Artifacts URL: https://flows.breyta.ai/docs/guide-output-artifacts.md HTML: https://flows.breyta.ai/docs/guide-output-artifacts Last updated: 2026-05-20T03:00:25+02:00 # Output Artifacts (Final Output Viewers) ## Quick Answer Use this guide to shape flow final outputs for UI rendering using stable viewer envelopes, Markdown reports, and fenced `breyta-resource` embeds. For public/end-user and installed flow output, default to a single readable Markdown artifact. For public/end-user flows, treat the final output as the product surface: return the human-readable artifact first, and keep raw automation/debug payloads out of the public result unless they are explicitly the thing the user asked for. The default rich output pattern is a Markdown report. When that report needs real Breyta resources, embed them in the Markdown with fenced `breyta-resource` blocks so tables, charts, downloads, images, videos, nested Markdown, and structured resources appear naturally in the report. Flows always produce a final output: the value returned by the `:flow` form. The Breyta UI shows this final output as a user-facing artifact (separate from debug inspection): - On the run page, `Run data -> Artifacts` opens a dedicated artifact sidepeek. - The canonical deep-link remains the run **Output** page. - `Request` remains a debug entrypoint and is intentionally separate from artifacts. This document describes how flow authors can shape the final output for good presentation. The flow definition does not currently declare a separate output schema. The implicit contract is: ```text final value returned by :flow = run output ``` Breyta captures that raw value as the run's canonical `flow-output` resource, stores `:result-resource-uri` on the execution, and derives a user-facing viewer artifact from the same value. Author/debug surfaces may expose raw output, previews, step outputs, and created resources, but the main Output view should be treated as the user-facing artifact. ## Where users see output For each run, output can be accessed from two user-facing surfaces: | Surface | Purpose | |---|---| | Run page sidepeek (`Artifacts`) | Primary, quick in-context output inspection. | | Output route (`/:workspace-id/runs/:flow-slug/:run-id/output`) | Canonical full-page output view and shareable deep-link. | When a run has no output yet: - Running/pending runs show `Output not available yet.` - Terminal runs show `No output captured.` ## The viewer envelope (recommended) Return an **envelope map** with these namespaced keys: | Key | Meaning | |---|---| | `:breyta.viewer/kind` | Viewer type to render (allowlisted). | | `:breyta.viewer/value` | Value payload for that viewer. | | `:breyta.viewer/options` | Optional viewer config (title, alt text, etc.). | Example: Markdown report ```clojure {:breyta.viewer/kind :markdown :breyta.viewer/options {:title "Summary"} :breyta.viewer/value "# Report\n\nHello."} ``` Example: structured run metadata ```clojure {:breyta.viewer/kind :raw :breyta.viewer/options {:title "Run metadata"} :breyta.viewer/value {:report-id "daily-orders-2026-05-01" :generated-for "Operations" :open-orders 2 :closed-orders 1 :risk "low" :next-review "2026-05-01T15:00:00Z"}} ``` Example: image (typically a Breyta-generated signed URL) ```clojure {:breyta.viewer/kind :image :breyta.viewer/options {:title "Screenshot" :alt "Screenshot"} :breyta.viewer/value "https://example.com/image.png"} ``` Example: audio/video (typically a Breyta-generated signed URL) ```clojure {:breyta.viewer/kind :audio :breyta.viewer/options {:title "Audio"} :breyta.viewer/value "https://example.com/audio.wav"} ``` ```clojure {:breyta.viewer/kind :video :breyta.viewer/options {:title "Video"} :breyta.viewer/value "https://example.com/video.mp4"} ``` ## Markdown Resource Embeds Markdown is the recommended default for narrative output. When the report needs to include a real Breyta resource, return a Markdown viewer envelope and include a fenced `breyta-resource` block. Use normal Markdown table syntax when the rows are static explanatory content inside the report. Use `:view :table` only when the report should render a persisted Breyta table resource with query, aggregate, chart, download, or drilldown behavior. Resource embeds are snapshot-oriented. The output normalizer resolves the referenced resource into snapshot metadata on the Markdown artifact, and the Markdown renderer injects that snapshot where the fence appears. The end-user output remains one continuous Markdown render: text, tables, images, videos, and nested Markdown flow in document order instead of appearing as separate output cards. V1 supports snapshot embeds only. `:mode :snapshot` is the default, and `:mode :live` is intentionally not supported yet. If an author sets a non- snapshot mode, the renderer shows an inline unsupported-mode fallback instead of live-querying the resource. Use EDN or JSON inside the fenced block. EDN is convenient in flow definitions. YAML directives are not supported in v1. For a compact field-by-field lookup, use [Output Artifact Reference](../reference/REFERENCE_OUTPUT_ARTIFACTS.md). ````clojure {:breyta.viewer/kind :markdown :breyta.viewer/options {:title "Order report"} :breyta.viewer/value (str "# Order report\n\n" "Filtered open orders:\n\n" "```breyta-resource\n" "{:resource \"" (:uri orders-table) "\"\n" " :view :table\n" " :title \"Filtered open orders\"\n" " :table {:query {:select [\"order-id\" \"customer\" \"amount\"]\n" " :where [[\"status\" := \"open\"]]\n" " :sort [[\"created-at\" :desc]]\n" " :page {:mode :offset :limit 25}}\n" " :columns {\"amount\" {:label \"Amount\"\n" " :format {:display \"currency\" :currency \"USD\"}}}}}\n" "```\n")} ```` Supported fields: | Field | Meaning | |---|---| | `:resource` | Absolute `res://...` URI in the current workspace. | | `:view` | `:auto`, `:table`, `:markdown`, `:text`, `:json`, `:image`, `:audio`, `:video`, or `:download`. | | `:mode` | `:snapshot` in v1. This is the default. `:live` is reserved and not supported. | | `:title` | Display title or caption for the embedded resource, where the adapter uses one. For embedded tables, this is metadata only; put visible section headings in the surrounding Markdown. | | `:label`, `:name` | Alternate display-name fields when `:title` is not present. | | `:table` | Table query and presentation options for `:view :table`. | ### Resource Embed Pattern Reference Use this table to choose the right embed shape: | Pattern | Use when | Minimal directive | |---|---|---| | `:view :auto` | Let Breyta choose from resource metadata/content type. | `{:resource "...", :view :auto}` | | `:view :table` with `:query` | Show a bounded filtered/sorted row snapshot. | `{:resource "...", :view :table, :table {:query {...}}}` | | `:view :table` with `:aggregate` | Show grouped metrics from a table resource. | `{:resource "...", :view :table, :table {:aggregate {...}}}` | | `:view :table` with `:chart` | Render a chart from the same query/aggregate rows. | `{:resource "...", :view :table, :table {:aggregate {...} :chart {...}}}` | | `:view :download` | Place a compact file/table download affordance in the report. | `{:resource "...", :view :download}` | | `:view :markdown` | Embed a persisted Markdown report/note. | `{:resource "...", :view :markdown}` | | `:view :text` | Embed a plain text resource. | `{:resource "...", :view :text}` | | `:view :json` | Embed persisted structured data as formatted raw output. | `{:resource "...", :view :json}` | | `:view :image` | Embed a persisted image blob. | `{:resource "...", :view :image, :alt "..."}` | | `:view :audio` | Embed a persisted audio blob. | `{:resource "...", :view :audio}` | | `:view :video` | Embed a persisted video blob. | `{:resource "...", :view :video}` | ### Table Query Embeds Table query embeds support bounded query options and column presentation: ```clojure {:resource "res://v1/ws/ws-123/result/table/tbl_orders" :view :table :title "Filtered open orders" :table {:query {:select [:order-id :customer :amount] :where [[:status := "open"]] :sort [[:created-at :desc]] :page {:mode :offset :limit 25}} :row-transform "(fn [row]\n (assoc row :customer (str (:customer row) \" - review\")))" :columns {"amount" {:label "Amount" :format {:display "currency" :currency "USD"}}}}} ``` Query fields: | Field | Meaning | |---|---| | `:select` | Ordered fields to include in the snapshot. Omit only when the default table preview is acceptable. | | `:where` | Vector of filters, for example `[["status" := "open"]]`. Keep filters deterministic and bounded. | | `:sort` | Ordered sort clauses, for example `[["created-at" :desc]]`. | | `:page` | Required bounded page options. Use `{:mode :offset :limit N}` for v1 Markdown embeds. | Column presentation fields: | Field | Meaning | |---|---| | `:label` | Human column heading. Prefer business labels over storage names. | | `:format` | Display formatting, for example currency or timestamp. | | `:semantic-type` / `:type-hint` | Optional metadata when the table viewer can use it. | Common format examples: ```clojure {:columns {"amount" {:label "Amount" :format {:display "currency" :currency "USD"}} "created-at" {:label "Created" :format {:display "timestamp"}} "owner-email" {:label "Owner" :format {:display "email"}}}} ``` For partitioned table families, pin the table/partition explicitly when the snapshot needs to be deterministic: ```clojure {:resource "res://v1/ws/ws-123/result/table/tbl_orders_by_year" :view :table :title "2026 open orders" :table {:partition-key "2026" :query {:select [:order-id :status :amount] :where [[:status := "open"]] :page {:mode :offset :limit 25}}}} ``` Use `:partition-key` for one table, or the existing table target shapes `:partition-keys` / `:partitions` when the table service query supports them. If the resource is a family and no partition is specified, the renderer follows the table service default; authors should specify the partition for stable final output snapshots. ### Table Aggregate Embeds The same table resource can be embedded more than once with different views. For aggregate snapshots, use `:table {:aggregate ...}` instead of `:table {:query ...}`: ```clojure {:resource "res://v1/ws/ws-123/result/table/tbl_orders" :view :table :title "Orders by status" :table {:aggregate {:group-by [:status] :metrics [{:op :count :as "orders"} {:op :sum :field :amount :as :total-amount}] :order-by [[:status :asc]] :limit 10} :columns {"status" {:label "Status"} "orders" {:label "Orders"} "total-amount" {:label "Total" :format {:display "currency" :currency "USD"}}}}} ``` Aggregate fields: | Field | Meaning | |---|---| | `:group-by` | Fields to group rows by. | | `:metrics` | Metric definitions. Supported metric maps include `:op`, optional `:field`, and `:as`. | | `:order-by` | Ordered result sort clauses. | | `:limit` | Bounded aggregate result size. Defaults to 25 rows and is capped at 100 rows. | Metric examples: ```clojure {:metrics [{:op :count :as "orders"} {:op :sum :field :amount :as :total-amount} {:op :avg :field :amount :as :average-order-value}]} ``` ### Chart Embeds From Tables Table snapshots can also render a Breyta chart above the visible table rows. The chart uses the same bounded query or aggregate result as the table, so the rendered graph and grid stay in sync. In Markdown embeds, chart/table titles are not rendered as extra chrome; put user-facing headings in the Markdown around the resource block. ```clojure {:resource "res://v1/ws/ws-123/result/table/tbl_orders" :view :table :title "Orders by status" :table {:aggregate {:group-by [:status] :metrics [{:op :count :as "orders"}] :order-by [[:status :asc]] :limit 10} :chart {:title "Order count by status" :x "status" :series [{:field "orders" :label "Orders" :type :bar}]}}} ``` Use `:type :line` for trend charts from the same table rows: ```clojure {:resource "res://v1/ws/ws-123/result/table/tbl_orders" :view :table :title "Order value trend" :table {:query {:select [:created-at :amount] :sort [[:created-at :asc]] :page {:mode :offset :limit 25}} :columns {"created-at" {:label "Created" :format {:display "timestamp"}} "amount" {:label "Order value" :format {:display "currency" :currency "USD"}}} :chart {:x "created-at" :series [{:field "amount" :label "Order value" :type :line}]}}} ``` Chart options: | Field | Meaning | |---|---| | `:title` | Optional chart title metadata. Full table resource panels may render it; Markdown embeds keep it out of the document chrome. | | `:x` | Label/category field. Defaults to the first visible field. | | `:series` | Vector of series maps. Each map supports `:field`, `:label`, and `:type` (`:bar` or `:line`). | | `:height` | Optional chart height in pixels. Defaults to 220 and is clamped to 120-480. | | `:y-min`, `:y-max` | Optional y-domain overrides. Defaults include zero. | Chart x-axis and hover labels reuse the column display format for the `:x` field. For date or timestamp period labels, set the matching column `:format` instead of letting raw ISO strings become the chart labels. Charts include zero in the y-domain by default; use explicit `:y-min` and `:y-max` only when a custom scale is intentional. ### Row Transform Use `:row-transform` when the final table needs presentation logic that is more specific than labels and built-in cell formats. The transform runs once while the Markdown resource snapshot is produced, after the bounded query or aggregate has resolved and before the run output is stored. ```clojure {:resource "res://v1/ws/ws-123/result/table/tbl_orders" :view :table :table {:query {:select [:order-id :customer :amount] :where [[:status := "open"]] :page {:mode :offset :limit 25}} :row-transform "(fn [row]\n (assoc row :customer\n (str (:customer row) \" - \"\n (if (< (:amount row) 10)\n \"batch pickup\"\n \"monitor cutoff\"))))"}} ``` The function receives one row map and must return one row map. String field names from table rows also receive safe keyword aliases for transform input, so authors can usually use `(:amount row)` in EDN examples. Use this only for final display shaping; durable data normalization belongs in the flow steps that create the resource. Keep transform source small; oversized transform forms are rejected before sandbox evaluation. For the available sandbox helpers, see the [`breyta.sandbox` and `json` helper reference](/docs/reference-step-function#limits-and-behavior). ### Download Embeds Rendered table embeds are document-native: they omit the table card header, per-table copy actions, CSV controls, and pagination footers. Put source/export affordances exactly where they belong in the report by adding a separate `:view :download` resource fence. For table resources, `:view :download` defaults to CSV. ```clojure {:resource "res://v1/ws/ws-123/result/table/tbl_orders" :view :download :title "Orders source CSV"} ``` Use `:format :csv` when you want to make the format explicit. Partitioned table families can use the same top-level partition fields as table embeds: ```clojure {:resource "res://v1/ws/ws-123/result/table/tbl_orders_by_year" :view :download :title "2026 orders CSV" :partition-key "2026" :format :csv} ``` For non-table file/blob resources, `:view :download` renders a compact download affordance using the resource content type: ```clojure {:resource "res://v1/ws/ws-123/result/blob/monthly-report.pdf" :view :download :title "Monthly report PDF"} ``` ### Markdown, Text, And JSON Embeds Use readable resource embeds when a report should inline another persisted artifact. Persisted Markdown: ```clojure {:resource "res://v1/ws/ws-123/result/blob/analyst-note.md" :view :markdown :title "Analyst note"} ``` Plain text: ```clojure {:resource "res://v1/ws/ws-123/result/blob/dispatch-note.txt" :view :text :title "Dispatch note"} ``` JSON: ```clojure {:resource "res://v1/ws/ws-123/result/blob/run-metadata.json" :view :json :title "Run metadata"} ``` Use `:view :json` for persisted JSON resources inside Markdown. For final output that is itself a map, return a `:raw` viewer envelope instead of trying to put the map into Markdown prose. ### Image, Audio, And Video Embeds Media embeds use persisted Breyta blob resources and signed content URLs. They do not require public bucket access. Image: ```clojure {:resource "res://v1/ws/ws-123/file/blob/.../dashboard.svg" :view :image :title "Fulfillment lane snapshot" :alt "Fulfillment lane dashboard snapshot"} ``` Audio: ```clojure {:resource "res://v1/ws/ws-123/file/blob/.../call-summary.wav" :view :audio :title "Call summary audio"} ``` Video: ```clojure {:resource "res://v1/ws/ws-123/file/blob/.../packing-station.mp4" :view :video :title "Packing station clip"} ``` ### Auto Embeds Use `:view :auto` when the resource content type is enough for Breyta to choose the viewer. Prefer explicit views in public/end-user reports when the output contract matters. ```clojure {:resource "res://v1/ws/ws-123/result/blob/analyst-note.md" :view :auto :title "Analyst note"} ``` The final Markdown output exposes one floating top-right copy action for the whole report instead of one copy action per embedded table. The visible renderer uses the resolved Breyta resource snapshots. The copied Markdown is a portable export: table snapshots become Markdown tables, Markdown resources become normal Markdown, text resources become fenced `text` code blocks, and JSON/raw resources become pretty-printed fenced `clojure` code blocks. Resource-backed links in copied Markdown intentionally point back into Breyta when a resource URI is known. Image embeds are copied as a Markdown image whose source is an absolute Breyta content URL, wrapped in a link to the Breyta resource viewer. Audio, video, and download embeds are copied as normal Markdown links to the Breyta resource viewer. Embeds that cannot be converted remain in the original source form. Keep embeds bounded: - include `:page {:mode :offset :limit N}` for table embeds - include `:limit N` for aggregate table embeds when the default 25-row snapshot is not right - use `:row-transform` only for bounded final display shaping - keep resource URIs in the same workspace as the run - keep each `breyta-resource` directive small and use the documented values for `:view`, `:mode`, and `:format` - use `:mode :snapshot`; live resource-backed pagination is a future contract - expect the final run output to render as a single Markdown document; the raw `breyta-resource` fences are an authoring/source shape, not the user-visible output ## Structured Maps And JSON Do not paste raw EDN/JSON maps into normal Markdown paragraphs. Paragraph text is prose, so structured values can collapse into a single hard-to-read line. Use one of these patterns instead: | Need | Pattern | |---|---| | The structured value is the product output | Return `{:breyta.viewer/kind :raw ...}` with the map as `:breyta.viewer/value`. | | The structured value is a persisted JSON resource inside a report | Use a Markdown `breyta-resource` fence with `:view :json`. | | The value is supporting detail inside Markdown | Put it in a fenced code block with `clojure`, `edn`, or `json`. | | The value should be a human summary | Convert it into prose, bullets, or a Markdown table before returning it. | Markdown code-block example: ````markdown ```clojure {:report-id "daily-orders-2026-05-01" :generated-for "Operations" :open-orders 2 :closed-orders 1 :risk "low" :next-review "2026-05-01T15:00:00Z"} ``` ```` JSON resource embed example: ````markdown ```breyta-resource {:resource "res://v1/ws/ws-123/result/blob/run-metadata.json" :view :json :title "Run metadata"} ``` ```` ## Breyta-managed media (no public URL required) Breyta media artifacts are usually stored as managed blobs. The browser still needs a fetchable `src`, but it can be a time-limited URL generated by Breyta instead of a public URL. Recommended pattern: persist the media as a blob and return the persisted blob result (Breyta will mint/refresh a signed URL when rendering the run Output page): ```clojure (let [download (flow/step :http :download-video {:connection :api :path "/video.mp4" :method :get :persist {:type :blob :tier :ephemeral :content-type "video/mp4"}})] {:breyta.viewer/kind :video :breyta.viewer/options {:title "Video"} :breyta.viewer/value download}) ``` Tip: you can also return the persisted blob result directly and let the UI infer the viewer (it uses `:content-type` and fetches a signed URL as needed): ```clojure (let [download (flow/step :http :download-audio {:connection :api :path "/audio.wav" :persist {:type :blob :tier :ephemeral :content-type "audio/wav"}})] download) ``` For HTTP-downloaded media, prefer `:tier :ephemeral` on the `:http` step unless the artifact is intentionally durable and should live like a retained file beyond the immediate workflow. Persisted blob outputs carry both resource and storage shapes: `:uri` / `:resource-uri` are the canonical resource refs for UI/API handoff, while `[:blob-ref :path]` is the concrete blob storage path used by loaders and storage-backed steps. Notes: | Note | Implication | |---|---| | Signed URLs expire. | UI refreshes signed URLs at render time. | | Need long-lived download links. | Generate fresh signed URL via new run or dedicated download flow. | ### Multi-part output (group, recommended) Use `:group` when you want to return multiple artifacts in one run. In v1 this is the recommended pattern for multi-artifact output: ```clojure {:breyta.viewer/kind :group :breyta.viewer/items [{:breyta.viewer/kind :markdown :breyta.viewer/options {:title "Summary"} :breyta.viewer/value "# Hello"} {:breyta.viewer/kind :image :breyta.viewer/options {:title "Image"} :breyta.viewer/value "https://example.com/image.png"} {:breyta.viewer/kind :raw :breyta.viewer/options {:title "Raw"} :breyta.viewer/value {:ok true :data [1 2 3]}}]} ``` ## Human-Readable Tables Choose the table surface based on what the user needs to do with the result: | Need | Output shape | |---|---| | A short comparison or summary inside a report | Put a Markdown table inside a `:markdown` artifact. | | A real grid for scanning, copying, exporting, or opening as a table resource | Persist rows with `:persist {:type :table ...}` and return the persisted table resource ref from a `:table` viewer. | For a real Breyta table, return the persisted table step result. Inline maps with `:rows`, `:columns`, `:schema`, or `:query` are preview data, not table resources. ```clojure (let [run-id (str "run-" (flow/now-ms)) comparison-table (flow/step :function :build-comparison-table {:input {:rows comparison-rows :run-id run-id} :code '(fn [{:keys [rows run-id]}] {:rows (mapv (fn [row] {:run_id run-id :paragraph (:paragraph row) :original (:original row) :cleaned (:cleaned row) :changed (:changed row)}) rows)}) :persist {:type :table :table (str "transcript-comparison-" run-id) :rows-path [:rows] :write-mode :upsert :key-fields [:run_id :paragraph] :columns [{:column :paragraph :display-name "Paragraph"} {:column :original :display-name "Original"} {:column :cleaned :display-name "Cleaned"} {:column :changed :display-name "Changed"}]}})] {:breyta.viewer/kind :table :breyta.viewer/options {:title "Original vs cleaned"} :breyta.viewer/value comparison-table}) ``` For multi-part output, put the table item first only when the grid is the primary artifact. Otherwise put a Markdown report first and the persisted table second: ```clojure {:breyta.viewer/kind :group :breyta.viewer/options {:title "Transcript QA"} :breyta.viewer/items [{:breyta.viewer/kind :markdown :breyta.viewer/options {:title "Summary"} :breyta.viewer/value report-markdown} {:breyta.viewer/kind :table :breyta.viewer/options {:title "Paragraph comparison"} :breyta.viewer/value comparison-table}]} ``` Verify real table output before release: - `:breyta.viewer/kind` is `:table` for the table artifact. - `:breyta.viewer/value` has `:type :resource-ref`. - `:breyta.viewer/value` has `:content-type "application/vnd.breyta.table+json"`. - `[:breyta.viewer/value :preview :rows-written]` or `[:breyta.viewer/value :preview :row-count]` is greater than zero. - `breyta resources read ` returns rows. ## Public flow output contract Public and installable flows need a presentation contract, not just an automation payload. Before release, decide what a non-author should see after a manual run and return that shape directly. | Do | Why | |---|---| | Put the primary human artifact first in `:breyta.viewer/items`. | The first artifact is the strongest signal for run Output and artifact surfaces. | | Default to one readable `:markdown` artifact. | Markdown can include headings, summaries, links, images, small tables, and video/file links without exposing raw data. | | Use `:table` only when users need a real grid for scanning, comparing, copying, or exporting rows. | Tables are useful for datasets, but they should not be the default public output. | | Use `:image`, `:video`, or `:audio` only when a dedicated media viewer is clearly better. | Dedicated media viewers are best for Breyta-managed blobs and signed URL handling. | | Use human column labels such as `Creator`, `Followers`, `Contact`, and `Profile URL`. | Public output should not expose database/debug fields. | | Persist internal tables/blobs for automation, then keep their refs out of the final public result unless users need them. | Resource refs and internal tables can be promoted or inspected in ways that expose implementation details. | | Use `:raw` only when the intended public output is structured data. | Raw output is a debug/automation surface by default. | Avoid mixing public presentation and internal automation output: ```clojure ;; Bad for public manual runs: exposes internal resources and raw debug data. {:breyta.viewer/kind :group :breyta.viewer/items [{:breyta.viewer/kind :markdown :breyta.viewer/options {:title "Report"} :breyta.viewer/value report-markdown} {:breyta.viewer/kind :raw :breyta.viewer/options {:title "Structured output"} :breyta.viewer/value output-data}] :lead-table-ref (:uri lead-table) :candidate-table-ref (:uri candidate-table) :run-table-ref (:uri runs-table)} ``` Prefer a curated public result with markdown first: ```clojure ;; Good for public manual runs: readable markdown first, optional table second. {:breyta.viewer/kind :group :breyta.viewer/options {:title "Influencer research results"} :breyta.viewer/items [{:breyta.viewer/kind :markdown :breyta.viewer/options {:title "Human-readable report"} :breyta.viewer/value report-markdown} {:breyta.viewer/kind :table :breyta.viewer/options {:title "Outreach-ready leads"} :breyta.viewer/value lead-table}] :status run-status :summary human-summary} ``` For table outputs, persist only user-facing rows and labels. - Good public labels: `Who`, `Followers`, `Contact`, `Link`, `Why relevant` - Avoid: debug previews, table ids, resource refs, status counters - If the table is short or explanatory, keep it inside Markdown Before release, run live or installation target and open Output. Confirm the first artifact is readable, and no raw viewer, internal ref, or implementation-only field is visible. ## No-login public artifact previews Use public artifact preview links when a generated artifact needs to be shared with someone outside the workspace, such as creator outreach, prospect review, or a claim/edit onboarding path. Public artifact preview links are different from run Output links and signed resource URLs: | Surface | Login | Intended use | |---|---:|---| | Run Output route | Required | Workspace/user run inspection. | | `breyta resources url` / `/api/resources/url` | Usually auth to mint; temporary signed content URL | Direct resource access for operators and runtime handoff. | | `/public/artifact-previews/:token` | No login | Read-only, unlisted, noindex preview page for outreach or external review. | | `/public/artifact-previews/:token/download` | No login | Token-scoped download for text-like shared artifacts such as EDN, Markdown, CSV, JSON, XML, JavaScript, form data, and plain text. | Create and revoke preview links through the authenticated resource-share API: ```http POST /api/resources/shares DELETE /api/resources/shares/:token GET /public/artifact-previews/:token GET /public/artifact-previews/:token/download ``` Send `X-Breyta-Workspace: ` on the authenticated create and revoke API calls. Create request body: ```json { "uri": "res://v1/ws//result/...", "title": "Creator preview", "ttlSeconds": 3600, "claimUrl": "/signup?source=creator-preview", "allowDownload": true } ``` `ttlSeconds` is optional. The default is 30 days, the minimum is 60 seconds, and the maximum is 90 days. `claimUrl` is optional and must be a same-origin relative path; unsafe absolute or protocol-relative URLs are ignored. `allowDownload` is optional and defaults to false. Enable it only when the resource itself is intended to be delivered as a file to the recipient. Public previews render the artifact in a content-only mode: - no workspace, run, workflow, debug, trace, or raw resource metadata - no private `/api/resources/content` proxy URLs - no `res://` refs, `uri` aliases, private resource-content URLs, or common signed storage URLs in rendered artifact fields - no resource drilldown, metadata popovers, Open, Export, or Download actions - public previews render the already-created artifact; unresolved `breyta-resource` fences and media/download URLs stay hidden instead of being fetched through private resource routes - page responses include `Cache-Control: no-store` and noindex robot headers When `allowDownload` is true and the artifact is text-like, the create response also returns `publicDownloadUrl`. The download route uses the same unlisted token, expiry, and revoke state as the preview page. It serves a bounded attachment with `Cache-Control: no-store` and without exposing the private `/api/resources/content` route or signed storage URLs. Binary media and sandboxed HTML previews are not downloadable through this public route. Production smoke check: 1. Create a preview share for a low-risk real output artifact. 2. Open the returned `publicUrl` in an incognito/no-login browser. 3. Confirm the safe human-facing content renders and the CTA points to the expected relative claim path. 4. If the artifact is text-like, open `publicDownloadUrl` and confirm the response is an attachment with the expected content and no private metadata. 5. Search the HTML for `res://`, `/api/resources/content`, `workspace=`, `workflow-id`, `run-id`, `debug`, `trace`, `X-Goog`, and storage host names. None should appear. 6. Revoke the share and confirm the preview and download URLs stop working. ## Inference (optional, no envelope) If you don’t use an envelope, the UI may infer a media viewer from common shapes: ```clojure {:url "https://example.com/file.png" :content-type "image/png"} ``` ```clojure {:signed-url "https://example.com/file.wav" :content-type "audio/wav"} ``` If inference doesn’t match what you want, wrap the value in an explicit envelope. ## Supported viewers (currently) | Viewer | Use | |---|---| | `:raw` | Fallback for arbitrary structured output. | | `:text` | Plain text content. | | `:markdown` | Rich text rendering. | | `:table` | Tabular row output with curated human-readable columns. | | `:image` | Image URL/blob rendering. | | `:audio` | Audio URL/blob rendering. | | `:video` | Video URL/blob rendering. | | `:download` | Compact file/table download affordance. | | `:group` | Multi-part output in one envelope. | ## JSON compatibility If the final output is JSON (string keys), the UI also recognizes: | JSON key | Meaning | |---|---| | `"breyta.viewer/kind"` | Viewer type. | | `"breyta.viewer/value"` | Viewer payload value. | | `"breyta.viewer/options"` | Optional viewer config. | | `"breyta.viewer/items"` | Multi-part items for `:group`. | ## Guidance | Guidance | Why | |---|---| | Prefer explicit envelopes for end-user-facing flows. | Produces predictable rendering behavior. | | Keep outputs reasonably sized. | UI truncates large raw outputs by default. | | Prefer URLs/resource refs for media over huge inline strings. | Better performance and reliability; Data URIs suit only small demos. | ## Related - [Start Here](/docs/start-here) - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [Output Artifact Reference](../reference/REFERENCE_OUTPUT_ARTIFACTS.md) - [CLI Commands](/docs/reference-cli-commands) - [Runs And Outputs](/docs/operate-runs-and-outputs) - [Persisted Results And Resource Refs](/docs/guide-persisted-results-and-resources) ## Generating images from AI APIs (base64 responses) Many image APIs return base64 strings instead of URLs. Do not pass base64 directly as `:breyta.viewer/value`; persist binary bytes first. When responses may exceed 512 KB, persist the HTTP step first. Then hydrate with `:load` before decoding base64. The working pattern is three steps: 1. **Call and persist the image API response** — HTTP step with `:persist {:type :blob}` 2. **Load, decode, and persist the binary image** — Function step with `:input {:resp resp}` and `:load [:resp]` 3. **Return a viewer envelope** — Use the persisted image blob as the viewer value ```clojure (let [;; Step 1: Call and persist the image API response resp (flow/step :http :generate-image {:connection :image-api :method :post :path "/images/generations" :timeout 120 :json {"model" "gpt-image-1.5" "prompt" prompt "size" "1024x1024" "quality" "low" "output_format" "jpeg"} :persist {:type :blob :tier :ephemeral :filename "image-response.json"}}) ;; Step 2: Load the persisted HTTP response, decode base64 → binary bytes, persist as blob ;; breyta.sandbox/base64-decode-bytes must be called with full namespace img (flow/step :function :save-image {:input {:resp resp} :load [:resp] :persist {:type :blob :filename "image.jpeg" :content-type "image/jpeg"} :code '(fn [{:keys [resp]}] (-> resp :body :data first :b64_json breyta.sandbox/base64-decode-bytes))})] ;; Step 3: Return the persisted image blob as the viewer value {:breyta.viewer/kind :image :breyta.viewer/options {:title "Generated image"} :breyta.viewer/value img}) ``` Use `:tier :ephemeral` on the HTTP response persist when that response is just a temporary handoff. The derived image blob persisted from the function step uses the retained default today. For multiple images, use `:group`: ```clojure {:breyta.viewer/kind :group :breyta.viewer/items [{:breyta.viewer/kind :image :breyta.viewer/value landscape-img :breyta.viewer/options {:title "Landscape"}} {:breyta.viewer/kind :image :breyta.viewer/value portrait-img :breyta.viewer/options {:title "Portrait"}}]} ``` **Why not return the base64 string directly as the blob?** Storing a base64 string with `:content-type "image/jpeg"` creates a text blob. The UI will not render it as an image. The `breyta.sandbox/base64-decode-bytes` step converts it to actual binary bytes first. **Inline result size limit** Base64 image responses often exceed the 512 KB inline limit. Persist first when size is uncertain, then hydrate with `:load`. See also: [`breyta.sandbox` helpers in Step Function reference](/docs/reference-step-function) ### API response formats — common gotchas Different image APIs return data differently. Make sure you're reading from the right path: | API | Base64 response path | Notes | |---|---|---| | OpenAI gpt-image-1 / gpt-image-1.5 | `(-> resp :body :data first :b64_json)` | Always base64. `output_format` must be `"jpeg"`, `"png"`, or `"webp"` — not `"b64_json"` (DALL-E syntax). | | OpenAI DALL-E 3 | `(-> resp :body :data first :b64_json)` | Only when `response_format: "b64_json"`. Default returns a URL — skip the decode step. | | Google Imagen (Vertex AI) | `(-> resp :body :predictions first :bytesBase64Encoded)` | Content type in `(-> resp :body :predictions first :mimeType)`. | --- Document: Patterns And Do/Do Not URL: https://flows.breyta.ai/docs/guide-patterns-do-dont.md HTML: https://flows.breyta.ai/docs/guide-patterns-do-dont Last updated: 2026-05-19T09:54:50+02:00 # Patterns (Do/Do Not) ## Quick Answer Use this guide for proven Breyta flow patterns and anti-patterns across bindings, templates, persistence, and orchestration. ## Configuration flow | Pattern | Why | |---|---| | Declare `:requires` in the flow definition. | Makes runtime dependencies explicit and bindable. | | Configure required slots/inputs with `flows configure`. | Binds slots and activation inputs without code edits. | | Use `flows release` only for explicit rollout/governance. | Keeps authoring and rollout concerns separated. | | Use explicit run targeting only when needed (`--target live`, `--installation-id`). | Keeps default authoring runs simple and deterministic. | ## Template usage | Pattern | Why | |---|---| | Put large payloads in `:templates`. | Reduces flow payload size and review noise. | | Reference with `:template` and `:data`. | Keeps step configs concise and reusable. | ## Output sizing | Pattern | Why | |---|---| | Estimate output size before adding data-producing steps. | Avoids late failures from size caps. | | Keep inline outputs small and predictable. | Maintains stable orchestration payloads. | | Default to `:persist` for unknown/unbounded size. | Prevents inline overflow for exports/pagination/files. | | Pass refs downstream (`:body-from-ref`, `:from-ref`). | Avoids rebuilding large inline payloads repeatedly. | ## Cross-flow handoff | Pattern | Why | |---|---| | Persist shared state to KV and read with `:kv` operations. | Supports structured handoff across runs/flows. | | Use deterministic keys (workspace + period + shard/page). | Keeps retries idempotent and traceable. | | Use `flow/call-flow` directly for same-app children in a linked public install. | The parent install context carries the source app, selected plan, and binding profile to app children. | | Pass an explicit `:installation-id`, `:profile-id`, or live target when calling children outside the same app/install context. | Prevents missing-runtime-target failures for child `:requires`. | | Use `:fanout` only for bounded child-workflow batches (`:call-flow` items). | Keeps concurrency enforceable and avoids reopening generic parallel side effects. | | Keep async child fanout at one orchestration depth. | Prevents recursive child spawning from exhausting shared runtime budgets. | ## Polling | Pattern | Why | |---|---| | Use `flow/poll` for external completion checks. | Provides bounded deterministic polling behavior. | | Set `:timeout` or `:max-attempts`. | Prevents unbounded polling loops. | | Use `:return-on` and `:abort-on`. | Makes success/fail conditions explicit. | | Prefer `:backoff` over manual interval math. | Centralizes retry interval control. | | Set `:id` for sleep step stability. | Improves trace/test consistency. | ## Working copy vs release | Command | Effect | |---|---| | `flows push` | Creates/updates working copy and draft target. | | `flows release` | Publishes an immutable release and updates live install for the selected workspace by default. | ## Do And Do Not | Do | Do not | |---|---| | Keep flow body deterministic. | Hardcode secrets in steps. | | Use `:function` steps for transforms. | Use `map`/`filter`/`reduce` in flow body. | | Include a manual interface for discoverability. | Call nondeterministic functions in flow body. | | Use connection slots for credentials. | Keep large/unpredictable payloads inline instead of `:persist`. | ## Public reusable flows | Pattern | Why | |---|---| | Name reusable/public flows around one clear query intent. | Stronger landing pages and easier discovery in search and AI-search tools. | | Use outcome-first titles with the main integration when useful. | Helps users understand value quickly. | | Rewrite visible step `:title` labels into plain-language actions. | Improves public-page quality and operator readability. | | Keep slug, title, description, and tags aligned around the same use case. | Prevents mixed search signals and thin page positioning. | ## Related - [Start Here](/docs/start-here) - [Flow Naming](/docs/build-flow-naming) - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [flow/poll](/docs/reference-flow-poll) - [CLI Commands](/docs/reference-cli-commands) --- Document: Templating URL: https://flows.breyta.ai/docs/reference-templating.md HTML: https://flows.breyta.ai/docs/reference-templating Last updated: 2026-02-13T15:21:05+01:00 # Templating ## Quick Answer Use this reference for Breyta Handlebars templating syntax, helpers, and practical template composition patterns. Breyta Flows templates use Handlebars (`{{...}}`) via the Java implementation (`handlebars-java`). ## Basics | Pattern | Syntax | Use | |---|---|---| | Variables | `{{id}}`, `{{user.name}}` | Read scalar or nested fields from input data. | | Conditionals | `{{#if condition}}...{{/if}}`, `{{#unless condition}}...{{/unless}}` | Branch rendered content based on truthy/falsey values. | | Loops | `{{#each items}}...{{/each}}` | Render repeated blocks from arrays/lists. | | Context switch | `{{#with obj}}...{{/with}}` | Temporarily scope lookup to a nested object. | ## Custom helpers available These helpers are available in the Flows runtime: | Helper | Example | Behavior | |---|---|---| | `truncate` | `{{truncate text length=100 suffix="..."}}` | Truncates text to a max length with optional suffix. | | `json` | `{{json data}}`, `{{json data pretty=true}}` | Serializes values to compact or pretty JSON. | | `length` | `{{length items}}` | Returns collection/string length. | | `join` | `{{join items separator=", "}}` | Joins list values with a separator. | | `first` | `{{first items}}` | Returns first element from a collection. | | `last` | `{{last items}}` | Returns last element from a collection. | | `default` | `{{default value fallback}}` | Returns `fallback` when `value` is missing/empty. | ## Practical examples ### LLM prompt template ```clojure {:id :findings :type :llm-prompt :system "Today is {{current-date}}." :prompt "Analyze: {{text}}"} ``` Called as: ```clojure (flow/step :llm :findings {:connection :ai :template :findings :data {:current-date "2026-01-16" :text "..."}}) ``` ### Choosing between templates When conditional logic gets messy, prefer two templates and select one in code: ```clojure (let [tpl ^{:label "Template variant" :yes "Include timestamps" :no "No timestamps"} (if (:include-timestamps? ctx) :findings-with-ts :findings-no-ts)] (flow/step :llm :findings {:connection :ai :template tpl :data ctx})) ``` ## Related - [Start Here](/docs/start-here) - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [CLI Commands](/docs/reference-cli-commands) --- Document: Index URL: https://flows.breyta.ai/docs/reference-index.md HTML: https://flows.breyta.ai/docs/reference-index Last updated: 2026-05-21T16:29:37+02:00 # Reference Index ## Quick Answer Use this page as the docs map when you know the topic but not the exact page. For the default authoring path, start with [Author Flows](/docs/playbook-author-flows). For the full command catalog, use [CLI Commands](/docs/reference-cli-commands). ## Topic Lookup | Topic | Values / Scope | Reference | |---|---|---| | CLI lifecycle | Draft authoring, configure/check, run, release, promote, installations | [CLI Workflow](/docs/guide-cli-workflow) | | Authoring playbook | Template reuse, existing data, interfaces, lint/push, single-step development, resource refs | [Author Flows](/docs/playbook-author-flows) | | Debug playbook | Failed runs, UI mismatch, output proof, feedback reports | [Debug And Verify](/docs/playbook-debug-and-verify) | | Release/install playbook | Draft/live, release/promote, live target configuration, install-shaped proof | [Release And Install](/docs/playbook-release-and-install) | | Public/marketplace playbook | Discover, marketplace, public copy, paid/public surfaces, author approval | [Public And Marketplace](/docs/playbook-public-and-marketplace) | | Reliability playbook | Fanout, paging loops, concurrency, checkpoints, large artifacts | [Advanced Reliability](/docs/playbook-advanced-reliability) | | Command catalog | Canonical CLI surface and docs navigation | [CLI Commands](/docs/reference-cli-commands) | | Docs search | `breyta docs find`, query syntax, field filters, snippets, and explain mode | [Docs Search](REFERENCE_DOCS_SEARCH.md) | | Flow metadata | Grouping, display icon selection, publish description, public discover visibility, and app-owned paid plan catalogs | [Flow Metadata And Discover](/docs/reference-flow-metadata) | | Paid public flows | Stripe Connect seller onboarding, marketplace visibility, app monetization, plan catalogs, and public discover publishing | [Paid Public Flows](/docs/guide-paid-public-flows) | | Flow health | Incidents, digests, and cadence behavior | [Flow Health](REFERENCE_FLOW_HEALTH.md) | | Service accounts | Workspace machine principals, API keys, and worker auth | [Service Accounts](REFERENCE_SERVICE_ACCOUNTS.md) | | Public flow naming | Titles, slugs, descriptions, tags, step titles for reusable/public flows | [Flow Naming](/docs/build-flow-naming) | | n8n import | n8n workflow to Breyta flow migration | [n8n Import](/docs/guide-n8n-import) | | Slot types | `:http-api`, `:database`, `:blob-storage`, `:kv-store`, `:secret` | [Flow Configuration](/docs/guide-flow-configuration) | | Auth types | Outgoing HTTP: `:api-key`, `:bearer`, `:token`, `:basic`, `:aws-sigv4`; webhook interfaces: `:hmac-sha256`, `:signature`, `:ip-allowlist`, `:none` (`:none` not valid for webhook interfaces) | [HTTP Step](/docs/reference-step-http), [Webhooks And Secret Refs](/docs/guide-webhooks-and-secret-refs) | | Interfaces and schedules | `:manual`, `:http`, `:webhook`, `:mcp`, top-level `:schedules` | [Flow Definition](/docs/reference-flow-definition) | | Manual interface inputs and uploads | `:invocations {:default {:inputs [...]}}`, `:interfaces :manual`, `:file`, `:blob-ref`, `:resource`, `:multiple`, `:accept` | [Flow Definition — Invocation Inputs](/docs/reference-flow-definition#invocation-inputs) | | Worker requirements | `{:kind :worker ...}` inside `:requires` | [Flow Definition](/docs/reference-flow-definition) | | Run concurrency | `:singleton`, `:keyed`, `:supersede`, `:drain`, `:coexist` | [Run Concurrency](/docs/reference-run-concurrency) | | Step types | `:http`, `:llm`, `:agent`, `:db`, `:wait`, `:function`, `:job`, `:notify`, `:kv`, `:table`, `:files`, `:fanout`, `:sleep`, `:ssh`, `:search`, `:breyta` | [Step Reference](/docs/reference-step-reference) | | LLM image inputs | Uploaded/persisted image resources in `:llm` messages and `:agent` inputs for vision-capable models | [Step LLM — Image Resource Inputs](/docs/reference-step-llm#image-resource-inputs) and [Step Agent — Image Resource Inputs](/docs/reference-step-agent#image-resource-inputs) | | AWS Bedrock LLM | Bedrock Claude connections, `:backend :bedrock`, AWS SigV4 auth, model ids, and uploaded/reference files in LLM messages | [Flow Configuration](/docs/guide-flow-configuration#aws-bedrock-claude-connection-shape), [Step LLM — AWS Bedrock Claude](/docs/reference-step-llm#aws-bedrock-claude), [Step HTTP — AWS SigV4](/docs/reference-step-http#auth-variant-aws-sigv4-aws-sigv4) | | Agent step | Objective-driven agent with memory, plan/checkpoint, cost controls, evaluation, approval gates, tracing, and delegation. The agent-safe `:files` subset, `:table`, `:search`, and allowlisted `:breyta` control-plane tool can be exposed directly; broad side-effecting operations require packaged `:steps` | [Step Agent](/docs/reference-step-agent) | | Packaged steps | Flow-local qualified-keyword step wrappers with schema-backed input/output, direct `flow/step` invocation, and agent tool publication via `:tools {:steps [...]}` | [Packaged Steps](/docs/reference-packaged-steps) | | Agent definitions | Flow-local named agent configurations invocable from `flow/step` by qualified id and publishable as tools via `:tools {:agents [...]}` | [Step Agent — Agent Definitions](/docs/reference-step-agent) | | Breyta control plane | Workspace-scoped `:breyta` agent tools for controlled flow, run, resource, and docs operations | [Step Breyta](/docs/reference-step-breyta) | | MCP tools | Remote MCP endpoints bound as `:http-api` connections with `:backend :mcp`, exposed through top-level `:mcp` adapters and selected via `:tools {:mcp [...]}` | [Flow Definition — MCP Tool Adapters](/docs/reference-flow-definition#mcp-tool-adapters) | | Files step | `:resolve-source`, `:init-changeset`, `:list`, `:read`, `:search`, `:write-file`, `:apply-edit`, `:delete-file`, `:move-file`, `:materialize`, `:capture`, `:diff`, `:publish`, and `:open-change-request`, with `:git` as the first source protocol and GitHub as the built-in provider | [Step Files](/docs/reference-step-files) | | Job step | Jobs control plane submit/get/await step surface | [Step Job](/docs/reference-step-job) | | Fanout step | Bounded child-workflow spawn/collect with legacy sequential compatibility | [Step Fanout](/docs/reference-step-fanout) | | Table step | `:query`, `:get-row`, `:aggregate`, `:schema`, `:export`, `:update-cell`, `:update-cell-format`, `:set-column`, `:recompute`, `:materialize-join` | [Step Table](/docs/reference-step-table) | | DB backends | `:postgres`, `:mysql`, `:clickhouse`, `:bigquery`, `:firestore` | [Step DB](/docs/reference-step-db) | | Orchestration constructs | `let`, `do`, `if`, `if-not`, `when`, `when-not`, `cond`, `case`, `for`, `doseq`, `loop`, `recur` | [Orchestration Constructs](/docs/reference-orchestration-constructs) | | Jobs control plane | `flow/step :job` submit/get/await orchestration | [Jobs Control Plane](/docs/reference-flow-jobs) | | Flow helpers | `flow/poll`, `flow/now-ms`, `flow/elapsed?`, `flow/backoff` | [flow/poll](/docs/reference-flow-poll) | | Form field types | Setup fields and run inputs: `:string`, `:text`, `:number`, `:boolean`, `:select`, `:date`, `:email`, `:textarea`, `:password`, `:secret`, `:file`, `:blob-ref`, `:resource` | [Flow Definition — Invocation Inputs](/docs/reference-flow-definition#invocation-inputs) and [Installations](/docs/guide-installations) | | Installations | Subscribe, activation inputs, interfaces, and webhook uploads | [Installations](/docs/guide-installations) | | Profiles | Draft/live/installation target model and run selection | [Profiles](/docs/guide-profiles) | | Runs and outputs | Run inspection, waits, outputs, sidepeek surfaces, and output-page verification | [Runs And Outputs](/docs/operate-runs-and-outputs) and [Output Artifacts](/docs/guide-output-artifacts) | | Run cost estimates | `Avg Estimated Run Cost`, `:metering`, LLM token pricing, and `breyta runs show` cost breakdown fields | [Run Cost Estimates](/docs/guide-run-cost-estimates) | | Persisted resources | `res://` refs, blobs, tables, resource inspection, and how persisted resources become rendered output artifacts | [Persisted Results And Resource Refs](/docs/guide-persisted-results-and-resources) and [Output Artifacts](/docs/guide-output-artifacts) | | Runtime data shapes | Function input maps, CLI envelopes, resource refs, `:persist`, `:load`, and avoiding parser bloat | [Runtime Data Shapes](/docs/reference-runtime-data-shapes) | | Table resources | CLI storage filters, table-family limits, partitioning notes, and table output rendering | [Table Resources](REFERENCE_TABLE_RESOURCES.md) and [Output Artifacts](/docs/guide-output-artifacts) | | Troubleshooting / limits | Runtime hints, common failures, and platform bounds | [Troubleshooting](/docs/guide-troubleshooting) and [Limits And Recovery](/docs/reference-limits-and-recovery) | | Google Drive sync | Service-account flow operation pattern | [Google Drive Sync](/docs/guide-google-drive-sync) | | Output artifacts | Final output viewers, Markdown reports, `breyta-resource` embeds, raw output, JSON resource rendering, and installed-flow output contracts | [Output Artifacts](/docs/guide-output-artifacts) | | Output artifact reference | Field tables for viewer envelopes, resource embeds, table query/aggregate/chart options, downloads, media, and structured output | [Output Artifact Reference](REFERENCE_OUTPUT_ARTIFACTS.md) | ## Navigation Hubs - [Start Here](/docs/start-here) - [Author Flows](/docs/playbook-author-flows) - [Debug And Verify](/docs/playbook-debug-and-verify) - [Release And Install](/docs/playbook-release-and-install) - [Public And Marketplace](/docs/playbook-public-and-marketplace) - [Advanced Reliability](/docs/playbook-advanced-reliability) - [CLI Workflow](/docs/guide-cli-workflow) - [CLI Commands](/docs/reference-cli-commands) - [Docs Search](REFERENCE_DOCS_SEARCH.md) - [Flow Metadata And Discover](/docs/reference-flow-metadata) - [Paid Public Flows](/docs/guide-paid-public-flows) - [Flow Health](REFERENCE_FLOW_HEALTH.md) - [Service Accounts](REFERENCE_SERVICE_ACCOUNTS.md) - [Table Resources](REFERENCE_TABLE_RESOURCES.md) - [Step Reference](/docs/reference-step-reference) - [Persisted Results And Resource Refs](/docs/guide-persisted-results-and-resources) - [Runtime Data Shapes](/docs/reference-runtime-data-shapes) - [Run Cost Estimates](/docs/guide-run-cost-estimates) - [Output Artifacts](/docs/guide-output-artifacts) - [Output Artifact Reference](REFERENCE_OUTPUT_ARTIFACTS.md) - [Troubleshooting](/docs/guide-troubleshooting) ## Related - [Start Here](/docs/start-here) - [Flow Naming](/docs/build-flow-naming) - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [Step Reference](/docs/reference-step-reference) - [Run Concurrency](/docs/reference-run-concurrency) - [Orchestration Constructs](/docs/reference-orchestration-constructs) - [Jobs Control Plane](/docs/reference-flow-jobs) - [flow/poll](/docs/reference-flow-poll) - [CLI Commands](/docs/reference-cli-commands) - [Paid Public Flows](/docs/guide-paid-public-flows) - [Service Accounts](REFERENCE_SERVICE_ACCOUNTS.md) --- Document: Step Reference URL: https://flows.breyta.ai/docs/reference-step-reference.md HTML: https://flows.breyta.ai/docs/reference-step-reference Last updated: 2026-05-15T14:59:18+02:00 # Step Reference ## Quick Answer Use this page as the step hub: compare step families here, then jump to the detailed step page you need. For the broader docs map, use [Reference Index](/docs/reference-index). For the full CLI surface, use [CLI Commands](/docs/reference-cli-commands). Built-in step families use unqualified keywords like `:http` and `:files`. Flow-local packaged steps defined under top-level `:steps` use qualified keywords like `:github/open-pr` and are invoked through the same `flow/step` surface. Flow-local agent definitions under top-level `:agents` use qualified keywords like `:review/security` and are also invoked through `flow/step`. ## Step Families At A Glance | Step type | Core fields | Notes | |---|---|---| | `:http` | `:connection` or `:url`, `:path`, `:method`, `:query`, `:headers`, `:json`, `:body`, `:response-as`, `:client-opts`, `:persist`, `:auto-search-index?`, `:retry` | Large and binary bodies should use `:persist`; safe page GETs can be auto-indexed for workspace search. | | `:llm` | `:connection`, `:model`, `:messages` or `:prompt`, `:template`, `:data` | Use templates for long prompts. Multipart messages can include uploaded image resources for vision-capable models. | | `:agent` | `:objective`, `:inputs`, `:connection`, `:tools`/`:available-steps`, `:memory`, `:cost`, `:approval`, `:trace`, `:output-persist` | Agent with memory, approval, tracing, and bounded tools. Expose narrow built-ins directly; wrap broad side effects in packaged `:steps`. | | `:db` | `:database`, `:connection`, backend-specific fields | SQL/BigQuery use `:sql`/`:template` + params; Firestore uses `:collection`/`:where`. | | `:wait` | `:key`, `:notify`, `:timeout` | Supports human/webhook resume patterns. | | `:function` | `:code` or `:ref`, `:input`, `:load`, `:persist` | Map-oriented transforms; persist large or table-shaped outputs. | | `:job` | `:op`, plus `:job-type`, `:job-id`, or `:batch-id` depending on op | External jobs control plane submit/get/await surface. | | `:notify` | `:channels` (`:http`), optional `:template`, `:data` | External delivery channels only. | | `:kv` | `:operation`, `:key`, operation-specific fields (`:value`, `:ttl`, `:prefix`) | Useful for state handoff/cache patterns. | | `:table` | `:op`, `:table`, op-specific fields (`:where`, `:sort`, `:group-by`, `:metrics`, `:column`) | Bounded table-resource query, aggregate, export, authored-column, and derived-table surface; creation happens through `:persist {:type :table ...}`. | | `:files` | `:op`, `:source`, op-specific fields (`:query`, `:paths`, `:path-prefix`, `:changeset`, `:edit`, `:branch`, `:commit-message`, `:title`) | Source-tree read/search/edit/materialize/capture with persistent `changeset-ref` overlays; supports publish/change-request flows. | | `:fanout` | `:items`, optional `:max-concurrency`, `:on-error`, `:transform` | Async only for all-`:call-flow` child workflow items; legacy non-child items run on the sequential compatibility path. | | `:sleep` | one of `:seconds`, `:millis`, or `:duration` | Deterministic delay boundary. | | `:ssh` | `:connection`, `:command`, `:env`, `:workdir`, `:timeout` | Remote exec over SSH; for agents use `:ssh` kickoff + `:wait` callback. | | `:search` | `:query`, optional `:targets`, optional `:limit`, optional `:hydrate` | Workspace search across `:resources`, `:flows`, `:runs`, and `:connections`, with semantic resource retrieval when enabled and default resource content hydration for `:resources` hits. | | `:breyta` | `:op`, `:allow`, optional `:args`, optional `:mutations`, optional `:require-approval` | Workspace-scoped control-plane operations for agents, including flows, runs, docs, resources, and explicitly granted mutations. | ## Common Step Options | Option | Meaning | Typical use | |---|---|---| | `:title` | Readable step label | Improves timeline readability. | | `:timeout` | Step timeout seconds | Bound slow external operations. | | `:retry` | Retry policy | Handle transient external failures. | | `:on-error` | Error policy (`:fail`, `:skip`, `:checkpoint`) | Control failure handling behavior. | | `:persist` | Store outputs as refs | Keep large outputs out of inline payloads. | | `:metering` | Advisory per-step cost estimate | Add fixed API/tool costs to creator-facing run cost breakdowns. | | `:review` / `:confirm` | Human checkpoints | Add explicit approval/verification points. | For packaged steps, these common options still live on the outer `flow/step` wrapper. The packaged definition itself should focus on the simplified input/output contract for the wrapped built-in step. ## Detailed References | Reference | Scope | |---|---| | [Step HTTP](/docs/reference-step-http) | HTTP request/response and persistence behavior | | [Step LLM](/docs/reference-step-llm) | LLM message/prompt and tool usage | | [Step Agent](/docs/reference-step-agent) | Objective/input-oriented wrapper over the LLM tool loop | | [Step Breyta](/docs/reference-step-breyta) | Native workspace-scoped Breyta control-plane operations for agents | | [Step DB](/docs/reference-step-db) | Shared DB step concepts and backend split | | [Step DB SQL](/docs/reference-step-db-sql) | SQL backends (`:postgres`, `:mysql`, `:clickhouse`) | | [Step DB BigQuery](/docs/reference-step-db-bigquery) | BigQuery-specific DB behavior | | [Step DB Firestore](/docs/reference-step-db-firestore) | Firestore-specific DB behavior | | [Step Wait](/docs/reference-step-wait) | Wait/signal/timeout and approval flows | | [Step Function](/docs/reference-step-function) | `:function` step and deterministic transforms | | [Step Job](/docs/reference-step-job) | Jobs control plane submit/get/await step surface | | [Step Notify](/docs/reference-step-notify) | Notification channel delivery | | [Step KV](/docs/reference-step-kv) | KV read/write/list operations | | [Step Table](/docs/reference-step-table) | Table-resource query, aggregate, column-authoring, export, and bounded mutation operations | | [Step Files](/docs/reference-step-files) | Source-tree resolution plus persistent `changeset-ref` overlays for read/search/edit/materialize/capture/diff, then branch publish and pull-request operations over that same logical state | | [Step Fanout](/docs/reference-step-fanout) | Bounded child-workflow fanout and legacy compatibility behavior | | [Step Sleep](/docs/reference-step-sleep) | Sleep timing semantics | | [Step SSH](/docs/reference-step-ssh) | Remote exec and agent kickoff patterns | | [Step Search](/docs/reference-step-search) | Workspace search step behavior and limits | | [Packaged Steps](/docs/reference-packaged-steps) | Flow-local qualified-keyword step wrappers with schema, direct invocation, and agent tool publication | | [Persisted Results And Resource Refs](/docs/guide-persisted-results-and-resources) | Working with persisted refs/resources | | [Run Cost Estimates](/docs/guide-run-cost-estimates) | `:metering`, token pricing, and run cost breakdowns | ## Related - [Reference Index](/docs/reference-index) - [Start Here](/docs/start-here) - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [CLI Workflow](/docs/guide-cli-workflow) - [CLI Commands](/docs/reference-cli-commands) --- Document: Step HTTP URL: https://flows.breyta.ai/docs/reference-step-http.md HTML: https://flows.breyta.ai/docs/reference-step-http Last updated: 2026-05-21T16:29:37+02:00 # Step HTTP (`:http`) ## Quick Answer Use this reference for the `:http` step schema, required fields, persistence behavior, and large-payload safety defaults. Use for HTTP APIs. Prefer `:connection` with a `:http-api` slot. ## Canonical Shape Core fields: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:connection` | keyword/string | Yes* | Slot or connection id | | `:url` | string | Yes* | Full URL (ad hoc request) | | `:path` | string | No | Appended to connection base URL | | `:method` | keyword | No | `:get`, `:post`, `:put`, `:patch`, `:delete` | | `:headers` | map | No | Header map | | `:query` | map | No | Query params | | `:json` | map | No | JSON body | | `:form` | map | No | URL-encoded form body | | `:multipart` | vector | No | Multipart parts | | `:body` | any | No | Raw body | | `:body-from-ref` | map | No | Load body from a blob ref, resource URI, or canonical `resource-ref` | | `:content-type` | keyword/string | No | Body content type | | `:accept` | keyword/string | No | Accept header helper (`:json`, `:xml`, `:text`) or a normal MIME header string such as `"application/vnd.api+json"` | | `:response-as` | keyword | No | `:auto`, `:json`, `:jsonl`, `:sse`, `:text`, `:bytes`, `:edn`, `:xml`, `:yaml` | | `:include-headers` | vector | No | Return only selected response headers, e.g. `["location"]` | | `:parser-fn` | form | No | Custom parse function | | `:template` | keyword | No | `:http-request` template id | | `:data` | map | No | Template variables | | `:auth` | map | No | Step-level auth (if not from connection) | | `:client-opts` | map | No | Transport overrides | | `:timeout` | int | No | Timeout seconds (common option) | | `:retry` | map | No | Retry policy (common option) | | `:persist` | map | No | Persist response refs; choose retained default for durable artifacts or `:tier :ephemeral` for temporary streamed HTTP blobs | | `:metering` | map | No | Advisory fixed API/tool cost for run cost estimates | | `:auto-search-index?` | boolean | No | Control automatic page indexing for searchable HTTP GET results | \* Provide either `:connection` or `:url`. ## Limits And Behavior Request and parsing: - Prefer one body mode: `:json`, `:form`, `:multipart`, `:body`, or `:body-from-ref`. If both `:json` and `:body` are set, `:json` wins. - `:accept` accepts shortcuts such as `:json` or `"json"` and full MIME header values such as `"application/json;q=0.9,*/*;q=0.1"`. - `:response-as :auto` uses response `Content-Type` to choose `:json`, `:jsonl`, `:sse`, `:text`, or `:bytes`. - `:response-as :sse` is for finite `text/event-stream` responses, not indefinite listeners. - `:query` values must be scalar. Join repeated values into the API's expected string form before passing them. Persistence and size: - Inline results are intended to stay under `512 KB`; larger or binary payloads need `:persist {:type :blob ...}`. - If payload size is unknown (exports, pagination, generated files), add `:persist` from the start. - Use `:tier :ephemeral` for temporary streamed HTTP downloads, exports, generated media, and response blobs. - Keep the retained default for user-facing or durable artifacts. - For binary downloads, set `:response-as :bytes` and blob `:persist`; Breyta can stream the response directly to storage. - Persisted blob results include `:uri` / `:resource-uri` for resource APIs/UI and `:blob-ref` for downstream blob loading. - Downstream function steps can hydrate persisted HTTP responses with `:input {:resp http-step-result}` and `:load [:resp]`. - `:client-opts {:max-response-bytes ...}` caps both buffered responses and streamed blob persists. If omitted, streamed blob persists use the persist-tier cap. - Binary blobs are searchable by metadata/path context. Add `:persist {:search-index {...}}` when you have trusted domain text for a binary file. Storage paths: - `:persist :path` and `:filename` support `{{...}}` interpolation from step params and runtime fields. - `:persist :path` must be relative; do not include a leading `/` or `..`. - Use `:persist {:type :blob :path ... :filename ...}` for runtime-managed paths. - Use `:persist {:type :blob :slot ...}` when installers should control the storage binding/root. - `:persist :slot` must refer to a local `:blob-storage` requirement such as `:archive`. Redirects, templates, auth: - Automatic redirect following is disabled for SSRF protection. - A `3xx` result is marked with `:redirect? true` and `:redirect-location` when `Location` is present. - For public document systems, request `["location"]`, check `:redirect?`, and make a second explicit `:http` step to the returned URL. - Templates cover request shape; step-level keys like `:persist` stay on the step. - Auth must use secret references. Inline tokens or API keys are rejected. - Prefer connection auth, or use step-level auth with `:auth {:type :bearer|:token|:api-key|:aws-sigv4 :secret-ref :my-secret}`. - DRF-style APIs can use `:auth {:type :token ...}` for `Authorization: Token ` or `:auth {:type :bearer :prefix "Token" ...}`. - Header API-key auth can use `:auth {:type :api-key :location :header :param-name "x-api-key" ...}`. - AWS APIs that require Signature Version 4 can use `:auth {:type :aws-sigv4 :secret-ref :aws-creds :region "us-east-1" :service "bedrock"}`. The secret should hold `access-key-id`, `secret-access-key`, and optional `session-token`. - Custom headers may reference secrets directly: `:headers {"x-api-key" {:secret-ref :shotstack-key}}`. - Use `:metering` when this HTTP call has a known fixed per-request cost. Large uploads: - Prefer `:body-from-ref` and multipart `:from-ref` for uploads from prior persisted artifacts or resource-picker-selected files. - Supported refs: persisted blob envelopes, `{:blob-ref ...}`, raw `res://` URIs, `{:ref uri}`, `{:resource-uri uri}`, and resource-picker maps. - Resource URIs used with `:body-from-ref` / `:from-ref` must point at blob-backed resources in the same workspace. ## Automatic Page Indexing By default, safe page-like `GET` responses are copied into retained workspace storage and indexed for `:search`. By default, auto-indexing runs only when all of these are true: - The request succeeds with an HTTP status below `400` - The method is `GET` - The step does not already set `:persist` - The response body is text or bytes that can be read as text - The response content type is `text/html`, `application/xhtml+xml`, `text/markdown`, `text/x-markdown`, or article-like `text/plain` - Workspace storage and the resource store are available - The request is safe to index by default: no resolved auth, no sensitive auth headers, and no sensitive query params Plain text counts as article-like when the URL path looks like content, such as `/article`, `/blog`, `/post`, `/news`, or `/docs`. When the page is indexed, the step result includes: ```edn {:persisted-blob {...} :auto-search-indexed? true} ``` Breyta stores raw page bytes as a retained blob and adds bounded search text. Use explicit `:persist {:search-index ...}` for curated title, tags, source label, or search text. Use `:auto-search-index? false` to opt out: ```clojure (flow/step :http :read-page {:url "https://example.com/article" :method :get :response-as :text :auto-search-index? false}) ``` Use `:auto-search-index? true` only when an authenticated or sensitive-looking response should become searchable workspace data. Other guards still apply. ```clojure (flow/step :http :read-private-doc {:connection :docs :path "/internal/article" :method :get :response-as :text :auto-search-index? true}) ``` If you set explicit `:persist`, that config owns persistence and search-index metadata. Automatic page indexing will not add a second blob. ## Breyta Public Docs Retrieval Use `:http` when a flow needs to retrieve Breyta public docs at runtime. The `:search` step is workspace-scoped and does not include a docs target. Declare the docs API as a connection slot: ```clojure {:requires [{:slot :breyta-docs :type :http-api :label "Breyta docs API" :base-url "https://flows.breyta.ai" :auth {:type :none}}]} ``` Search pages first, then fetch the selected page as markdown: ```clojure '(let [doc-search (flow/step :http :find-breyta-docs {:connection :breyta-docs :path "/api/docs/pages" :method :get :query {:query (:question (flow/input)) :limit 5 :with-snippets "true"} :response-as :json}) first-page (first (get-in doc-search [:data :pages])) slug (:slug first-page) markdown (when slug (flow/step :http :read-breyta-doc {:connection :breyta-docs :path (str "/api/docs/pages/" slug) :method :get :query {:format "md"} :response-as :text}))] {:pages (get-in doc-search [:data :pages]) :selected-slug slug :markdown markdown}) ``` For direct HTTP without a slot, set `:url "https://flows.breyta.ai/api/docs/pages"`, but prefer the slot form when the base URL should be configurable or reused. ## Auth Variant: Google Service Account (`:google-service-account`) Use this when you need unattended OAuth access to Google APIs on a schedule (e.g., Drive folder sync). The flow mints access tokens from a **service account JSON** secret. Example (Google Drive list): ```clojure (flow/step :http :list-drive-files {:title "List Drive files" :url "https://www.googleapis.com/drive/v3/files" :method :get :query {:q "'' in parents and trashed = false" :fields "nextPageToken,files(id,name,mimeType,modifiedTime,size,driveId)" :pageSize 1000 :supportsAllDrives "true" :includeItemsFromAllDrives "true"} :auth {:type :google-service-account :secret-ref :google-drive-service-account :scopes ["https://www.googleapis.com/auth/drive.readonly" "https://www.googleapis.com/auth/drive.metadata.readonly"]}}) ``` ## Auth Variant: AWS SigV4 (`:aws-sigv4`) Use this for AWS APIs that require Signature Version 4, including Amazon Bedrock Runtime and API Gateway IAM endpoints. The auth config supplies non-secret request metadata: ```clojure :auth {:type :aws-sigv4 :secret-ref :aws-bedrock :region "us-east-1" :service "bedrock"} ``` The referenced secret stores the credentials: ```json { "access-key-id": "AKIA...", "secret-access-key": "...", "session-token": "optional temporary credential token" } ``` For Breyta-hosted Bedrock authoring, this SigV4 connection shape is the supported path today. Amazon Bedrock API keys use bearer-token auth separately and are not accepted by the `:backend :bedrock` LLM backend yet. For raw Bedrock Runtime calls, use a Bedrock Runtime host and a model invoke path: ```clojure (flow/step :http :bedrock-invoke {:connection :bedrock :path "/model/anthropic.claude-3-5-sonnet-20241022-v2%3A0/invoke" :method :post :json {:anthropic_version "bedrock-2023-05-31" :max_tokens 1024 :messages [{:role "user" :content [{:type "text" :text "Summarize this file."}]}]} :response-as :json}) ``` SigV4 signing uses the request method, path, query string, payload hash, `Host`, `x-amz-date`, and any stable headers that are sent with the request. Volatile transport headers such as `User-Agent` are sent normally but are not included in the canonical signed header list. Prefer the `:llm` Bedrock backend when the goal is a normal Claude model call; use raw `:http` only when you need a Bedrock Runtime operation that the LLM step does not expose. ## Google OAuth Upload Init For Google APIs that use an `/upload/...` endpoint, keep upload controls in scalar `:query` params and put OAuth on the connection: ```clojure (flow/step :http :init-youtube-upload {:connection :youtube :path "/upload/youtube/v3/videos" :method :post :query {:uploadType "resumable" :part "snippet,status"} :headers {"X-Upload-Content-Type" "video/mp4"} :json {:snippet {:title "Demo"} :status {:privacyStatus "private"}} :include-headers ["location"] :response-as :text}) ``` ## Canonical Example ```clojure ;; In the flow definition: ;; :templates [{:id :download-report ;; :type :http-request ;; :request {:path "/reports/latest" :method :get}}] ;; :functions [{:id :http-headers ;; :language :clojure ;; :code "(fn [input] {\"X-Request-Id\" (:request-id input)})"}] '(let [headers (flow/step :function :build-http-headers {:ref :http-headers :input (flow/input)}) users (flow/step :http :get-users {:connection :api :path "/users" :method :get :query {:limit 10} :headers headers :retry {:max-attempts 3 :backoff-ms 500}}) report (flow/step :http :download-report {:connection :api :template :download-report :data {:customer-id "cust-77" :report-id "rep-42" :run-date "2026-03-23"} :response-as :bytes :persist {:type :blob :slot :archive :path "{{data.customer-id}}/{{data.run-date}}" :filename "summary-{{data.report-id}}.pdf" :tier :ephemeral}})] {:users users :report report}) ``` Author the matching blob-storage slot like this: ```clojure {:requires [{:slot :archive :type :blob-storage :label "Archive storage" :config {:prefix {:default "reports" :label "Folder prefix" :placeholder "reports/customer-a"}}}]} ``` With `:persist {:slot :archive ...}`, platform-backed writes land under: ```text workspaces//storage/// ``` ## Related - [Start Here](/docs/start-here) - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [Templates](/docs/reference-templates) - [Limits And Recovery](/docs/reference-limits-and-recovery) - [CLI Commands](/docs/reference-cli-commands) - [Persisted Results And Resource Refs](/docs/guide-persisted-results-and-resources) --- Document: Packaged Steps URL: https://flows.breyta.ai/docs/reference-packaged-steps.md HTML: https://flows.breyta.ai/docs/reference-packaged-steps Last updated: 2026-05-12T10:46:32+02:00 # Packaged Steps (`:steps`) ## Quick Answer Use top-level `:steps` when you want to wrap a heavy built-in step config behind a simplified input/output contract that can be invoked directly from `flow/step` and published as an agent tool from `:agent` / `:llm`. Packaged steps are flow-local definitions — they live in the flow EDN alongside `:functions`, `:templates`, and `:flow`. They do not create new step families; they wrap one existing built-in step. For the full list of public step families you can call directly with `flow/step`, see [Step Reference](/docs/reference-step-reference). Public direct step families include `:agent` and `:breyta`; packaged steps are an additional wrapper pattern for supported built-in families when you need a simplified, schema-backed contract or agent tool publication. ## Direct Steps Vs Packaged Wrappers | Goal | Use | |---|---| | Call a public built-in step directly from orchestration, including `:agent` or `:breyta` | `flow/step` with the built-in step family | | Expose agent-safe built-in tools directly | `:available-steps [:files :table :search]`, string tool names in `:tools {:allowed [...]}`, or allowlisted `:tools {:breyta ...}` | | Expose broad raw operations safely to an agent | A packaged `:steps` wrapper with a narrow `:input-schema` | | Reuse a heavy config surface with simpler input/output | A packaged `:steps` wrapper | Do not read this page as the complete step-family list. It explains when to wrap a step config. The complete public list lives in [Step Reference](/docs/reference-step-reference), with separate pages for [Step Agent](/docs/reference-step-agent) and [Step Breyta](/docs/reference-step-breyta). ## When To Use Packaged Steps **Required** for agent tool use with these step types: - `:http`, `:db`, `:kv`, `:function`, `:notify`, `:sleep`, `:wait`, `:ssh` - These have broad raw parameter surfaces and cannot be exposed directly as agent tools via `:available-steps` or `:tools {:allowed [...]}`. - Direct `:files` tools are limited to the agent-safe operations `:init-changeset`, `:list`, `:read`, `:search`, `:write-file`, `:apply-edit`, `:replace`, `:replace-lines`, `:delete-file`, `:move-file`, and `:diff`. Wrap `:resolve-source`, `:capture`, `:publish`, and `:open-change-request` in packaged steps when an agent needs those capabilities. - If you try, you'll get a clear error: *"cannot be exposed directly as an agent tool. Wrap it in a packaged :steps definition."* **Not required** (but still useful) for agent-safe step types: - `:files` agent-safe operations, `:table`, `:search` - These have narrow, agent-oriented parameter schemas and can be exposed directly. **Also useful** when: - The underlying built-in step has a large config surface (connections, templates, complex request shaping) and you want a smaller invocation contract for flow authoring. - You want to publish a simplified tool interface to `:agent` without exposing the full built-in step config to the model. - You want input/output schema validation at the packaged boundary, separate from the wrapped step's own validation. - You have reusable `:prepare` or `:project-result` transforms that should run automatically when the step is invoked. ## Definition Shape Each packaged step definition is a map in the top-level `:steps` vector: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:id` | qualified keyword | Yes | Must be qualified, e.g. `:github/open-pr`, `:billing/fetch-order`. Unqualified keywords are reserved for built-in step families. | | `:type` | keyword | Yes | The supported wrapped built-in step type, such as `:http`, `:files`, `:llm`, `:table`, or `:db`. See [Step Reference](/docs/reference-step-reference) for all public direct step families; not every direct step family should be packaged. | | `:description` | string | Yes | Human-readable description. Used in validation errors, agent tool summaries, and the docs surface. Max 4000 chars. | | `:input-schema` | schema | Yes | schema definition for the packaged step input. Push-time validation checks the schema shape and static literal invocation keys; runtime validates actual invocation values. | | `:output-schema` | schema | No | schema definition for the projected output. Validated at runtime after `:project-result`. | | `:title` | string | No | Short display title. | | `:defaults` | map | No | Default config merged into the wrapped step. Use for `:connection`, `:template`, `:method`, `:path`, etc. | | `:prepare` | keyword | No | Id of a top-level `:functions` entry. Called with the validated input; must return a map that becomes the wrapped step config. | | `:project-result` | keyword | No | Id of a top-level `:functions` entry. Called with the raw wrapped step result; must return the projected output. | | `:tool` | map | No | Tool publication metadata for `:agent` / `:llm`. Contains optional `:name` and `:description` overrides. | ## Id Rules - Packaged step ids **must** be qualified keywords: `:namespace/name`. - Examples: `:github/open-pr`, `:billing/fetch-order`, `:slack/post-message`. - Unqualified keywords like `:http`, `:files`, `:table` are reserved for built-in step families and will fail validation. - Ids must be unique within a flow. Duplicate ids fail at push time. ## Canonical Example ```clojure {:functions [{:id :prepare-open-pr :language :clojure :code "(fn [{:keys [owner repo title head base]}] {:method :post :path (str \"/repos/\" owner \"/\" repo \"/pulls\") :json {:title title :head head :base base :draft true} :response-as :json})"} {:id :project-pr-result :language :clojure :code "(fn [result] {:url (get-in result [:body :html_url]) :number (get-in result [:body :number]) :state (get-in result [:body :state])})"}] :steps [{:id :github/open-pr :type :http :description "Open a draft GitHub pull request." :input-schema [:map [:owner :string] [:repo :string] [:title :string] [:head :string] [:base :string]] :output-schema [:map [:url :string] [:number :int] [:state :string]] :defaults {:connection :github-api} :prepare :prepare-open-pr :project-result :project-pr-result :tool {:name "github_open_pr" :description "Open a draft GitHub pull request."}}] :flow '(let [pr (flow/step :github/open-pr :create-pr {:owner "acme" :repo "service" :title "Fix null handling" :head "task/fix-null" :base "main" :persist {:type :blob}})] {:pr-url (:url pr) :pr-number (:number pr)})} ``` ## Direct Invocation Packaged steps are invoked through `flow/step` using the packaged step id instead of a built-in step type: ```clojure (flow/step :github/open-pr :step-id {:owner "acme" :repo "service" :title "Fix: handle null case" :head "task/fix-null" :base "main"}) ``` The runtime: 1. Validates the input against `:input-schema`. 2. Calls `:prepare` (if set) to transform input into wrapped step config. 3. Merges `:defaults` under the prepared config. 4. Validates the merged config against the wrapped built-in step schema. 5. Executes the wrapped built-in step. 6. Calls `:project-result` (if set) to transform the result. 7. Validates the projected result against `:output-schema` (if set). 8. Returns the projected result. ## Outer Step Controls Common step controls like `:retry`, `:timeout`, `:persist`, `:on-error`, `:breakpoint`, `:mock`, `:expect`, `:review`, and `:confirm` belong on the outer `flow/step` call, not inside the packaged step definition: ```clojure (flow/step :github/open-pr :create-pr {:owner "acme" :repo "service" :title "Fix it" :head "task/fix" :base "main" :retry {:max-attempts 3} :persist {:type :blob} :on-error {:strategy :continue}}) ``` These controls are separated from the packaged input before validation and reattached around the wrapped execution. ## Agent Tool Publication Packaged steps can be published as agent tools through the `:tools` config on `:agent` or `:llm` steps: ```clojure (flow/step :agent :review {:connection :ai :objective "Review and fix the issue." :tools {:steps [:github/open-pr :billing/fetch-order] :allowed ["files" "table"]} :max-iterations 8}) ``` When `:tools {:steps [...]}` lists packaged step ids: - The step's `:input-schema` becomes the tool's parameter schema. - The step's `:description` (or `:tool :description` override) becomes the tool description. - The step's `:tool :name` (or a sanitized version of the id) becomes the tool name. - Tool calls from the model are validated against the packaged input schema, then executed through the same prepare/execute/project pipeline. - The packaged step's `:defaults`, `:connection`, `:template`, and helper functions are resolved at runtime — the model only needs to provide the simplified input. Use string tool names in `:tools {:allowed [...]}`, for example `["files" "table"]`. If you want built-in step keywords instead, use `:available-steps [:files :table]`. This means a packaged step works identically whether invoked directly from `flow/step` or called as a tool by an agent. ## Connection-Scoped Tool Permissions For packaged steps that wrap connection-backed built-ins such as `:http`, you can add flow-local runtime guardrails on the corresponding `:requires` slot. These permissions are authored on the requirement, not on the workspace connection record itself, so the same connection can be used with different scopes in different flows. Example: ```clojure {:requires [{:slot :github-api :type :http-api :label "GitHub API" :auth {:type :none} :base-url "https://api.github.com" :config {:tool-permissions {:http {:mode :read-only :allowed-hosts ["api.github.com"] :allowed-path-prefixes ["/repos/breyta/breyta"]}}}}] :steps [{:id :github/list-pulls :type :http :description "List repository pull requests." :input-schema [:map] :defaults {:connection :github-api :path "/repos/breyta/breyta/pulls"}}]} ``` Supported generic HTTP permission hints are: - `:mode` — high-level access mode such as `:read-only` or `:read-write` - `:allowed-methods` — explicit allowlist of HTTP verbs - `:allowed-hosts` — host allowlist - `:allowed-path-prefixes` — allowed path prefixes under the resolved base URL - `:allowed-url-prefixes` — allowed absolute URL prefixes These guards are enforced at packaged-step runtime, including when the packaged step is published as an agent tool. The model still only sees the packaged step's simplified `:input-schema`, but runtime also verifies that the wrapped step stays within the authored connection scope. ## Prepare and Project-Result Functions `:prepare` and `:project-result` reference top-level `:functions` by id. **`:prepare`** transforms validated packaged input into the config map for the wrapped built-in step: ```clojure ;; Input: {:owner "acme" :repo "service" :title "Fix" :head "task/fix" :base "main"} ;; Output: {:method :post :path "/repos/acme/service/pulls" :json {...} :response-as :json} ``` If `:prepare` is omitted, the validated input is passed directly as the wrapped step config. **`:project-result`** transforms the raw wrapped step result into the packaged output: ```clojure ;; Input: {:status 201 :body {:html_url "..." :number 42 ...}} ;; Output: {:url "..." :number 42 :state "open"} ``` If `:project-result` is omitted, the raw wrapped step result is returned. Both functions execute in a secure sandbox with the same constraints as top-level `:functions`. ## Schema Validation **Push time:** The flow definition validator checks that: - All `:steps` entries match the `PackagedStepDefinition` schema. - Ids are qualified keywords with no duplicates. - `:prepare` and `:project-result` reference existing top-level `:functions`. - `:input-schema` and `:output-schema` are valid schema definitions. - Direct packaged-step invocations with literal map input only use the schema to catch unknown keys and missing required keys. Dynamic values and literal value types are validated at runtime because they may depend on workflow execution. **Runtime:** On each invocation: - Input is validated against `:input-schema` before `:prepare`. - The prepared config is validated against the wrapped built-in step schema. - Output is validated against `:output-schema` (if present) after `:project-result`. ## Local Source Includes Large packaged step definitions can live in sidecar files using the `#flow/include` form: ```clojure {:steps [#flow/include "flow-assets/steps/github-open-pr.edn" #flow/include "flow-assets/steps/billing-fetch-order.edn"]} ``` Includes are expanded client-side by `breyta flows push` before upload. ## Installation Notes Packaged steps often reference connections in `:defaults`: ```clojure {:steps [{:id :github/open-pr :type :http :defaults {:connection :github-api} ...}]} ``` The `:connection :github-api` reference is resolved through the flow's `:requires` bindings at runtime. In installed flows, the installer provides the connection during setup (or the author provides it via `:provided-by :author`). **Key rule:** Packaged step `:defaults` should reference connection **slots**, not hardcoded connection IDs. This way the binding resolves correctly in both the author's workspace and any installation. ```clojure ;; Good: references a slot that resolves through :requires :defaults {:connection :github-api} ;; Bad: hardcoded to the author's specific connection :defaults {:connection "pwtucmXckXmJGspGF4i2"} ``` When a packaged step is published as an agent tool via `:tools {:steps [...]}`, the same connection binding applies — the agent's tool call executes with the installation's bound connection, not a hardcoded one. ## Current Limitations - Packaged steps wrap **one** underlying built-in step. Multi-step and subflow-backed packaged steps are not supported. - `:prepare` and `:project-result` must reference top-level `:functions`. Inline code is not supported. - Packaged steps are flow-local. There is no cross-flow or workspace-level step registry. - `:defaults` are shallow-merged. Deep merge of nested config maps is not supported. ## Related - [Flow Definition](/docs/reference-flow-definition) — top-level `:steps` field - [Step Agent](/docs/reference-step-agent) — agent tool publication via `:tools {:steps [...]}` - [Installations](/docs/guide-installations) — installable agent flows - [Step Reference](/docs/reference-step-reference) — built-in step families - [Step Function](/docs/reference-step-function) — top-level `:functions` used by `:prepare` / `:project-result` --- Document: Step Agent URL: https://flows.breyta.ai/docs/reference-step-agent.md HTML: https://flows.breyta.ai/docs/reference-step-agent Last updated: 2026-05-19T13:06:48+02:00 # Step Agent (`:agent`) ## Quick Answer Use `:agent` when you want to give an AI model an objective, tools, and inputs and let it work toward the goal autonomously. The agent can call `:files`, `:table`, `:search`, the allowlisted `:breyta` control-plane tool, and any packaged step tools you provide. Use `:llm` instead if you need direct control over messages and prompts. ## Canonical Shape Core fields: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:type` | keyword | Yes | Must be `:agent` | | `:objective` | string | Yes | Plain-language goal for the step | | `:instructions` | string | No | Extra agent guidance appended to the system prompt | | `:inputs` | map | No | Typed scalar/resource inputs available to the agent | | `:template` | keyword | No | Optional `:llm-prompt` template id | | `:data` | map | No | Extra template data to merge with `:objective`, `:inputs`, `:input-summary`, `:tool-summary`, and `:instructions` | | `:expect` | map | No | Optional output expectation/assertion metadata | | `:connection` | keyword/string | Recommended | Slot or direct connection id for the model provider | | `:model` / `:provider` | scalar | No | Model/provider overrides | | `:temperature`, `:top-p`, `:stop` | scalar | No | Generation controls forwarded to the model provider | | `:max-tokens` | int | No | Response token cap for each model turn | | `:seed`, `:presence-penalty`, `:frequency-penalty` | scalar | No | Provider-supported generation controls | | `:reasoning-effort` | keyword/string | No | Reasoning effort for providers that support it | | `:prompt-cache-key`, `:previous-response-id`, `:cache-system?` | scalar/boolean | No | Provider-specific prompt caching and response continuation controls | | `:provider-opts` | map | No | Provider-specific escape hatch | | `:base-url`, `:deployment`, `:api-version` | string | No | Custom/Azure/Chat Completions-compatible/OpenRouter endpoint overrides | | `:tools` | map/vector | No | Canonical `:llm` tools envelope, including `:steps [...]` for packaged steps and `:agents [...]` for flow-level agent definitions | | `:available-steps` | vector | No | Built-in step types to expose as tools | | `:max-iterations` | int | No | Agentic loop cap when executing tools; default `20`, max `100` | | `:max-tool-calls` | int | No | Total executed tool-call cap; default `100`, max `1000` | | `:max-repeated-tool-calls` | int | No | Optional cap for identical tool name+arguments calls; unset by default, max `100` | | `:max-tokens-budget` | int | No | Cumulative token limit; loop exits gracefully when exceeded | | `:output` / `:response-format` | map/keyword | No | Structured output handling; `:output :schema` accepts Malli schemas or raw JSON Schema maps | | `:json-schema` | map | No | Legacy raw JSON Schema field | | `:output-persist` | boolean/map | No | Auto-persist agent output as a workspace resource (blob) after completion | | `:memory` | string/map | No | Agent memory backed by a `:table` resource (see Agent Memory section) | | `:cost` | boolean/map | No | Cost tracking and aggregate budget enforcement across runs | | `:evaluate` | boolean/map | No | Post-run output evaluation via a second LLM pass | | `:approval` | boolean/keyword/map | No | Approval gates for mutation tools or plan steps | | `:trace` | boolean/map | No | Capture full execution trace as a blob resource for debugging | | `:before-tool-call` | function | No | Hook called before a proposed tool batch executes | | `:after-tool-call` | function | No | Hook called after a tool batch executes | | `:auth` | map | No | Explicit auth block when not using `:connection` | | `:workspace-id` | string | No | Runtime context override; normally supplied by Breyta | Agent-safe step types available through `:available-steps` or `:tools {:allowed [...]}`: - `:files` — changeset overlays, read/search/edit/diff over existing source-tree or changeset refs - `:table` — table-resource query, aggregate, export, and bounded mutation - `:search` — workspace search across resources, flows, runs, and connections Control-plane tools configured separately in the canonical `:tools` envelope: - `:breyta` — allowlisted workspace-scoped operations for flows, runs, docs, and resources. Configure it as `:tools {:breyta {:allow [...]}}` so the operation allowlist and mutation mode are explicit. Step types that require a packaged `:steps` wrapper for agent tool use: - `:http`, `:db`, `:kv`, `:function`, `:notify`, `:sleep`, `:wait`, `:ssh` - `:files` operations with broad side effects or external provider access: `:resolve-source`, `:capture`, `:publish`, and `:open-change-request` - These have broad raw parameter surfaces. Wrap them in a flow-local packaged step definition with an explicit `:input-schema` to limit what the model can call. See `REFERENCE_PACKAGED_STEPS.md` or [Packaged Steps](/docs/reference-packaged-steps). Tool config note: - `:available-steps` uses built-in step keywords like `[:files :table]` - `:tools {:allowed [...]}` uses string tool names like `["files" "table"]` - `:tools {:steps [...]}` uses qualified packaged step ids like `[:github/open-pr]` - `:tools {:breyta {:allow [...]}}` exposes Breyta workspace operations; see [Step Breyta](/docs/reference-step-breyta) for operation args and mutation modes ## Full Config Example This example intentionally shows the broad authored surface. In normal flows, use only the fields you need. ```clojure (flow/step :agent :review {:connection :ai :provider :openai :model "gpt-5.2" :objective "Review the proposed changes and return JSON findings." :instructions "Use tools for facts. Do not speculate." :inputs {:draft changeset-ref :risk-level "high"} :template :agent-review :data {:style "concise"} :expect {:contains ["findings"]} ;; Model/provider controls inherited from :llm. :temperature 0.2 :top-p 0.9 :stop ["\nDONE"] :max-tokens 4000 :seed 42 :presence-penalty 0.0 :frequency-penalty 0.1 :reasoning-effort :medium :prompt-cache-key "review-v1" :previous-response-id "resp_previous" :cache-system? true :provider-opts {:openai {:responses {:store false}}} :base-url "https://api.openai.com/v1" :deployment "prod-reviewer" :api-version "2025-04-01-preview" ;; Tools. :available-steps [:files :table :search] :tools {:mode :execute :allowed ["files" "table" "search"] :breyta {:allow [:flows/list :runs/show] :mutations :none} :steps [:review/publish-changeset] :agents [:review/security] :require {:tool-names ["files"] :steps [:review/publish-changeset] :agents [:review/security]} :max-iterations 8 :max-tool-calls 80 :max-repeated-tool-calls 3} :max-iterations 20 :max-tool-calls 100 :max-repeated-tool-calls 4 :max-tokens-budget 120000 ;; Output and post-processing. Prefer Malli schemas in :output :schema. :output {:format :json :schema [:map [:findings [:vector :string]] [:risk [:enum "low" "medium" "high"]]] :style :deterministic :strict? true} ;; Legacy compatibility fields. Omit these when using :output above unless ;; you intentionally want the top-level values to override canonical output. :response-format :json :json-schema {"type" "object"} :output-persist {:type :blob :name "review-report" :content-type "application/json"} ;; Agent runtime features. :memory {:table memory-table :scope :task :scope-key "review-42" :auto-recall true :auto-summarize true :recall-limit 20 :types ["observation" "decision"] :plan {:enforce true :max-steps 6 :strict false}} :cost {:enabled true :budget {:max-tokens-total 500000 :window-hours 720}} :evaluate {:criteria [:relevance :completeness :accuracy] :model "gpt-4o-mini" :persist-to-memory true} :approval {:mode :tools :tool-names #{"files" "publish_changeset"}} :trace {:name "review-trace"} :before-tool-call (fn [{:keys [proposed-tool-calls]}] nil) :after-tool-call (fn [{:keys [tool-calls last-results]}] nil) ;; Usually prefer :connection over explicit :auth. :auth {:type :api-key :header "Authorization" :prefix "Bearer"} :workspace-id "ws-runtime-override"}) ``` ## Runtime Behavior - Without `:template`: the agent builds its own prompt from `:objective`, `:inputs`, and `:instructions`. - With `:template`: `:objective`, `:inputs`, `:input-summary`, `:tool-summary`, and `:instructions` are merged into `:data` for the template. - Packaged step tools (`:tools {:steps [...]}`) run through their wrapped step with all defaults and connection bindings applied. - Result shape is the same as `:llm`. - Agentic loop defaults: - `:max-iterations` defaults to `20` and may be set up to `100`. - `:max-tool-calls` defaults to `100` and may be set up to `1000`. - `:max-repeated-tool-calls` is unset by default; set it to stop repeated identical tool calls. - When a value is present both top-level and inside `:tools`, the `:tools` envelope value wins for that run. ## Tool Guidance The agent's system prompt tells the model which tools are available and what they do. This includes both built-in step tools and packaged step tools. Template-backed runs receive the same information under `:tool-summary`. Example `:tool-summary`: ```clojure {:enabled [{:tool :files :name "files" :description "Inspect, search, diff, and edit existing source-tree and changeset resources." :ops ["init-changeset" "list" "read" "search" "write-file" "apply-edit" "replace" "replace-lines" "delete-file" "move-file" "diff"]} {:tool :table :name "table" :description "Query, aggregate, inspect, export, and mutate bounded table resources." :ops ["query" "get-row" "aggregate" "schema" "export" "update-cell" "update-cell-format" "set-column" "recompute" "materialize-join"]} {:tool :breyta :name "breyta" :description "Call allowed Breyta workspace control-plane operations in the current workspace." :ops ["flows/list" "runs/show"]}] :enabled-steps [:files :table] :missing []} ``` ## Input Enrichment Resource refs in `:inputs` are automatically summarized for the model: - Source trees → compact handles with `:files` tool access hints - Changesets → handles with both read and write hints - Tables → handles with `:table` tool access hints - Image resources → visual context in the first model message when the agent is not using a prompt template and the selected provider/model supports image input - Missing tools are flagged (e.g. "files tool is not enabled for this run") - Long strings are truncated in the summary Template-backed runs receive both raw `:inputs` and the enriched `:input-summary`. ### Image Resource Inputs Pass uploaded or persisted image resources in `:inputs` when an agent should inspect visual context: ```clojure (flow/step :agent :review-screenshot {:connection :ai :model "gpt-4o" :objective "Review the screenshot and list UX issues." :inputs {:screenshot uploaded-image-ref}}) ``` For non-template agent runs, image resources are added to the agent's initial multipart model message. The prompt still includes the normal input summary, and the resource bytes are resolved later inside the `:llm` activity. You can also pass an explicit image resource content part when you need detail, transport, or processing options: ```clojure :inputs {:screenshot {:type :image-resource :resource uploaded-image-ref :detail :high :transport :data-url :processing {:max-width 1600 :max-height 1600 :format :jpeg :quality 85}}} ``` Notes: - Image inputs require a vision-capable provider/model and fail before the provider call if the selected model is text-only. - Template-backed agent runs receive raw `:inputs` and `:input-summary`; they do not automatically add image parts to the initial message. - If a tool returns an image resource during an agentic loop, Breyta can append it as visual context on the next model turn when the model supports image input. Text-only models keep the image reference in the normal tool result. - Image resources follow the same guardrails as `:llm`: resource-system-only reads, content type/signature checks for data URLs, byte and pixel limits, optional re-encoding, and short-lived signed URL transport when supported. Example enriched input summary: ```clojure {:repo-tree {:kind :source-tree :uri "res://v1/ws/ws-acme/bundle/source-tree/git/source-123" :content-type "application/vnd.breyta.source-tree+json" :recommended-tool :files :tool-enabled? true :access {:tool :files :read-args {:source "res://v1/ws/ws-acme/bundle/source-tree/git/source-123"} :common-ops [:list :read :search :materialize]}} :draft {:kind :changeset :uri "res://v1/ws/ws-acme/bundle/changeset/files/changeset-123" :recommended-tool :files :tool-enabled? true :access {:tool :files :read-args {:source "res://v1/ws/ws-acme/bundle/changeset/files/changeset-123"} :write-args {:changeset "res://v1/ws/ws-acme/bundle/changeset/files/changeset-123"} :common-ops [:list :read :search :diff :write-file :apply-edit :replace :replace-lines :delete-file :move-file :materialize :publish :open-change-request]}} :incident-table {:kind :table :uri "res://v1/ws/ws-acme/result/table/tbl_123" :recommended-tool :table :tool-enabled? true :access {:tool :table :args {:table {:ref "res://v1/ws/ws-acme/result/table/tbl_123"}} :common-ops [:query :get-row :aggregate :schema :export]}}} ``` If a enriched input recommends a built-in tool that is not enabled, the summary adds a `:missing-tool` constraint like: ```clojure {:repo-tree {:kind :source-tree :recommended-tool :files :tool-enabled? false :missing-tool {:tool :files :name "files" :constraint "Hydrated inputs recommend the files built-in tool, but it is not enabled for this agent run. Do not invent files tool calls."}}} ``` ## Result Shape `:` `agent` returns the same canonical result envelope as `:llm`. In the common text case, the flow receives a map like: ```clojure {:content "final answer" :tool-calls [{:tool-call-id "call_1" :name "files" :result {...} :success true :duration-ms 1250}] :tool-call-stats {:call-count 3 :success-count 2 :error-count 1 :total-duration-ms 4200} :usage {:prompt-tokens 123 :completion-tokens 45 :total-tokens 168} :tokens-used 168 :model "gpt-5-mini" :provider :openai :finish-reason "stop" :iterations 3} ``` - `:content` — the agent's final answer. Use this in downstream steps. - `:tool-calls` — each tool call with `:name`, `:result`, `:duration-ms`. - `:tool-call-stats` — aggregate: call count, success/error counts, total duration. - `:iterations` — how many turns the agent took. - `:usage` / `:tokens-used` — token accounting. - With `:output {:format :json}`, `:content` is parsed JSON data. - With `:output {:format :json :schema }`, the schema is compiled to provider-facing JSON Schema before the final model turn. ## Structured Output Schemas For new agent flows, prefer a Malli schema in the canonical `:output` envelope: ```clojure (flow/step :agent :triage {:connection :ai :objective "Triage the support request." :inputs {:email email-text} :output {:format :json :schema [:map [:summary :string] [:priority [:enum "low" "normal" "high"]] [:recommended-actions [:vector :string]]]}}) ``` Breyta validates the Malli schema form, converts it to provider-facing JSON Schema, and sends that JSON Schema through the same provider path used by `:llm`. This keeps `:agent` and `:llm` authoring consistent while preserving provider-specific schema behavior. Raw JSON Schema maps are still accepted in `:output :schema` and are passed through unchanged. The older top-level `:response-format` and `:json-schema` fields remain supported for existing flows. If both forms are present, the top-level legacy fields win for backward compatibility. Provider support is not uniform. DeepSeek supports JSON mode with `:output {:format :json}`, but the standard DeepSeek chat endpoint does not accept Breyta JSON Schema structured output. For DeepSeek agents, use JSON mode without `:schema`, or add a deterministic validation/repair step after the agent result. OpenRouter supports agentic tool loops through Breyta's OpenRouter provider when the selected routed model supports tool calling. For agent-to-agent delegation, list flow-level agents under `:tools :agents`; add `:tools {:require {:agents [...]}}` when the orchestrator must call specific specialist agents before producing its final answer. Generic Chat Completions-like endpoints are not enabled for agentic loops by default. If you have verified that an endpoint/model can replay assistant `tool_calls` and accept following `role=tool` messages, register a flow-level LLM provider with `:family :chat-completions-compatible` and explicit `:capabilities #{:tool-calling :structured-output :agentic-tool-loop}`. ## Tool Call Hooks Use `:before-tool-call` and `:after-tool-call` when authored flow logic needs to inspect or gate the agentic loop. Both hooks can update available tools or abort the loop early. ```clojure (flow/step :agent :fix-and-publish {:connection :ai :objective "Fix the issue, then publish when ready." :inputs {:changeset changeset} :available-steps [:files] :before-tool-call (fn [{:keys [proposed-tool-calls usage]}] (when (> (:total-tokens usage) 50000) {:abort true :content "Stopping: token budget exceeded" :abort-reason "token-budget"})) :after-tool-call (fn [{:keys [iteration tools tool-calls last-results usage]}] ;; After the first edit, make an authored packaged publish tool available. (when (and (pos? iteration) (some #(= "files" (:name %)) tool-calls)) {:tools (assoc tools :publish_changeset {:type :review/publish-changeset :name "publish_changeset"})})) :max-iterations 8}) ``` `:before-tool-call` receives: - `:iteration` — the upcoming iteration number (1-based) - `:tools` — current tool definitions map - `:tool-calls` — all tool calls executed so far - `:proposed-tool-calls` — tool calls proposed for this iteration - `:usage` — cumulative token usage `:after-tool-call` receives: - `:iteration` — the just-completed iteration number (1-based) - `:tools` — current tool definitions map - `:tool-calls` — all tool calls executed so far - `:last-results` — tool results from the most recent iteration - `:usage` — cumulative token usage It can return: - `nil` — continue unchanged - `{:tools }` — continue with updated tools - `{:abort true :content "..." :abort-reason "..."}` — stop the loop early Errors in the hook are caught and logged; the loop continues. ## Agent Memory When `:memory` is configured, the agent gets built-in memory capabilities backed by a normal `:table` resource. ### Memory Config | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:table` | resource ref/URI | Yes | The memory table resource | | `:scope` | keyword | No | `:flow` (default), `:task`, or `:global` — filters recalled memories | | `:auto-recall` | boolean | No | Pre-load recent memories into context (default `true`) | | `:auto-summarize` | boolean | No | Persist a run summary after completion (default `false`) | | `:recall-limit` | int | No | Max memories to pre-load (default `20`) | | `:types` | vector | No | Filter recalled memory types | ### What Happens At Runtime 1. **Before the loop:** The runtime queries the memory table for recent entries scoped to this flow and injects them into the system prompt as "Prior memories." The agent starts with context from previous runs. 2. **During the loop:** Two built-in tools are available automatically: - `memory_recall` — query the memory table by type, key, or freely - `memory_record` — record an observation, decision, or working-state entry 3. **After the loop:** If `:auto-summarize` is true, a `run-summary` entry is persisted with iteration count, tool call stats, and finish reason. ### Memory Entry Types - `"observation"` — facts discovered during investigation (decays as code changes) - `"decision"` — choices made and the reasoning behind them (persists until constraint changes) - `"working-state"` — progress on an in-flight task (ephemeral, per-task) - `"run-summary"` — auto-generated completion record (created by `:auto-summarize`) ### Memory Example ```clojure '(let [;; Create or reuse a memory table memory-table (flow/step :table :ensure-memory {:op :schema :table memory-table-ref :persist {:type :table :name "agent-memory"}}) ;; Agent with memory result (flow/step :agent :review {:connection :ai :objective "Review the code and record your findings." :inputs {:repo-tree repo-tree} :available-steps [:files] :memory {:table memory-table-ref :scope :flow :auto-summarize true} :max-iterations 8})] result) ``` The memory table is a normal Breyta workspace resource — visible, inspectable, and exportable. Multiple flows can share the same memory table for cross-flow agent context. ## Plan / Checkpoint Support When `:memory {:plan {:enforce true}}` is configured, the agent must declare a plan before acting, and can checkpoint progress step by step. Plan config: `:enforce` (require plan), `:max-steps` (default 10), `:strict` (abort on deviation), `:checkpoint` (persist completion status). Two built-in tools are registered: `memory_plan` (declare steps) and `memory_checkpoint` (mark step done). Plan steps are persisted to the memory table as `plan-step` entries, so the next run sees prior plan progress. ```clojure :memory {:table memory-ref :plan {:enforce true :max-steps 6 :strict false} :auto-summarize true} ``` In strict mode, the loop aborts if the agent's first tool call is not `memory_plan`. In soft mode (default), a warning is logged but execution continues. ## Output Persistence The optional `:output-persist` config auto-persists the agent's final `:content` as a workspace resource after completion: ```clojure ;; Simple: persist as blob with auto-detected content type :output-persist true ;; Explicit: named blob with specific content type :output-persist {:name "review-report" :content-type "text/markdown"} ``` When enabled, the result includes `:persisted-ref` with the resource reference. If persistence fails, a warning is logged and the result is returned without the ref. This eliminates the common pattern of adding a separate `:persist` step after every agent call. ## Cost Controls When `:cost` is configured, the agent tracks cumulative token usage across runs through a KV-backed cost ledger. ```clojure :cost {:enabled true :budget {:max-tokens-total 500000 :window-hours 720}} ;; 30-day window ``` - **Pre-run:** Checks the aggregate budget. If exceeded, throws `:resource-exhausted` with the current usage and window details. - **Post-run:** Records the run's token count, iteration count, and tool call count to the ledger. The ledger is scoped per flow slug and retains the last 100 entries. - **Per-run:** `:max-tokens-budget` on the step config caps a single run independently of the aggregate budget. ## Evaluation When `:evaluate` is configured, a second LLM pass scores the agent's output after completion. ```clojure :evaluate {:model "gpt-4o-mini" ;; cheaper model for eval :criteria [:relevance :completeness :accuracy] :persist-to-memory true} ;; save scores to memory table ``` The result includes an `:evaluation` key: ```clojure {:evaluation {:scores {:relevance 5 :completeness 3 :accuracy 4} :overall 4 :summary "Good finding, but missed the second injection point." :evaluation-tokens 50}} ``` When `:persist-to-memory` is true and `:memory` is configured, evaluation scores are persisted as `run-summary` entries for trend tracking. The evaluation LLM call uses the same connection as the agent step. If the author provides the connection via `{:provided-by :author}` in `:requires`, the author pays for evaluation across all installations. Use `:model` in the evaluate config to pick a cheaper model for scoring. ## Approval Gates When `:approval` is configured, certain tool calls pause the agent loop and return an approval request instead of continuing. ```clojure ;; Pause on any mutation (writes, edits, publishes, deletes) :approval true ;; Pause on specific tools only :approval {:mode :tools :tool-names #{"files" "table"}} ;; Pause on specific plan steps :approval {:mode :plan-steps :step-names #{"publish" "open-pr"}} ``` When approval is triggered, the result includes `:abort? true` and an `:approval-request` with the pending actions: ```clojure {:abort? true :abort-reason "approval-required" :approval-request {:key "approval:3:..." :iteration 3 :actions [{:tool "files" :success true :duration-ms 1200}] :mode :all-mutations}} ``` The flow author catches this and wires it into a `:wait` step: ```clojure '(let [result (flow/step :agent :fix {:approval true ...})] (if (:abort? result) (do (flow/step :wait :approve {:key (get-in result [:approval-request :key]) :timeout "24h"}) ;; Re-run the agent after approval (flow/step :agent :fix-continue {...})) result)) ``` The agent's system prompt is updated to mention which actions require approval, so the model knows to expect pauses. ## Execution Trace When `:trace true` (or `:trace {:name "my-trace"}`) is configured, the runtime captures a full execution timeline and persists it as a single blob resource after completion. ```clojure :trace true ``` The trace captures per-iteration: LLM call metadata (model, tokens), tool calls with arguments/timing/results, memory operations, and plan state. The final document includes result summary, evaluation scores, and cost data. - **During the loop:** Zero storage writes — trace accumulates in memory - **After completion:** Single blob write with content-type `application/vnd.breyta.agent-trace+json` - **On mid-loop crash:** Trace is lost (the run's execution history is still available for crash debugging) The result includes `:trace-ref` with the resource URI. The trace blob is visible in the workspace resource UI for inspection and debugging. ## Agent Delegation A coordinator agent can delegate bounded subtasks to specialist sub-agents using packaged `:steps` with `:type :agent`: ```clojure {:steps [{:id :review/security-scan :type :agent :description "Run a focused security scan on the selected code area." :input-schema [:map [:area :string] [:repo-tree :any]] :defaults {:connection :ai :model "gpt-5-mini" :available-steps [:files] :max-iterations 5} :prepare :prepare-security-scan}] :flow '(let [result (flow/step :agent :coordinator {:connection :ai :objective "Review the repo, identify problem areas, then delegate a security scan for each." :inputs {:repo-tree repo-tree} :tools {:steps [:review/security-scan] :allowed ["files"]} :max-iterations 10})] result)} ``` How this works: - The coordinator agent sees `review/security-scan` as a tool with a simple `[:map [:area :string] [:repo-tree :any]]` input schema. - When the coordinator calls the tool, the packaged step runtime: 1. Validates the input against `:input-schema` 2. Calls `:prepare` to transform input into the sub-agent's config 3. Merges `:defaults` (connection, model, tools, iteration budget) 4. Executes `:type :agent` — a full nested agentic loop 5. Applies `:project-result` to the sub-agent's output - The sub-agent runs with its own iteration budget and tool set. - The coordinator gets the projected result back as a tool call output. This gives you multi-agent workflows without a new runtime concept — it's packaged steps all the way down. Not currently supported: - agent profiles and skills - automatic repo guidance loading (e.g. `AGENTS.md`) - embedding-based memory recall ranking ## Canonical Example ```clojure '(let [repo-tree (flow/step :files :resolve-repo {:op :resolve-source :source {:type :git :repo "https://github.com/acme/service.git" :ref "main" :connection :github-api}}) findings (flow/step :agent :review-and-fix {:connection :ai :model "gpt-5-mini" :objective "Inspect the repo and summarize the most likely fix for the incident." :instructions "Use the files and table tools before making claims." :inputs {:repo-tree repo-tree :incident-table {:type :resource-ref :uri "res://v1/ws/ws-acme/bundle/resource/table/incidents"}} :available-steps [:files :table :search] :max-iterations 6})] findings) ``` ## Template-Backed Example ```clojure '(flow/step :agent :plan-remediation {:connection :ai :template :planner :objective "Prepare a remediation plan for the failing run." :inputs {:repo-tree repo-tree :run-report run-report} :data {:service "billing"} :tools {:allowed ["files" "table" "search"] :mode :execute} :max-iterations 5}) ``` ## Using Packaged Steps As Agent Tools Top-level `:steps` definitions can be published directly as agent tools through `:tools {:steps [...]}`. This lets you define a simplified, schema-backed tool interface that the model calls, while the heavy built-in step config stays in the packaged definition. ### Step-to-Tool Example ```clojure {:functions [{:id :prepare-open-pr :language :clojure :code "(fn [{:keys [owner repo title head base]}] {:method :post :path (str \"/repos/\" owner \"/\" repo \"/pulls\") :json {:title title :head head :base base :draft true} :response-as :json})"} {:id :project-pr-result :language :clojure :code "(fn [result] {:url (get-in result [:body :html_url]) :number (get-in result [:body :number])})"}] :steps [{:id :github/open-pr :type :http :description "Open a draft GitHub pull request." :input-schema [:map [:owner :string] [:repo :string] [:title :string] [:head :string] [:base :string]] :output-schema [:map [:url :string] [:number :int]] :defaults {:connection :github-api} :prepare :prepare-open-pr :project-result :project-pr-result :tool {:name "github_open_pr" :description "Open a draft GitHub pull request."}}] :flow '(let [result (flow/step :agent :fix-and-pr {:connection :ai :objective "Fix the issue, then open a draft PR with the fix." :inputs {:repo-tree repo-tree :changeset changeset} :tools {:steps [:github/open-pr] :allowed ["files" "table"]} :max-iterations 8})] result)} ``` ### How It Works When the agent calls `github_open_pr` with `{:owner "acme" :repo "service" ...}`: 1. The packaged step's `:input-schema` validates the tool call arguments. 2. `:prepare` transforms the simplified input into the `:http` step config. 3. `:defaults` (including `:connection :github-api`) are merged. 4. The `:http` step executes the API call. 5. `:project-result` transforms the raw HTTP response into `{:url ... :number ...}`. 6. The projected result is returned to the model as the tool call output. The model only sees the simplified interface — it never needs to know about `:method`, `:path`, `:json`, `:connection`, or `:response-as`. ### Mixing Built-In and Packaged Tools `:tools` supports built-in steps, packaged steps, reusable agents, and MCP tool adapters simultaneously: ```clojure :tools {:steps [:github/open-pr :billing/fetch-order] :agents [:review/security] :mcp [:linear/list_issues] :allowed ["files" "table" "search"] :mode :execute} ``` Built-in step tools use their native published interface. Packaged step tools use the simplified `:input-schema` as their tool parameter schema. ### Tool Summary Packaged step tools appear in the `:tool-summary` alongside built-in tools: ```clojure {:enabled [{:tool :files :name "files" :description "..."} {:tool :table :name "table" :description "..."} {:tool :github/open-pr :name "github_open_pr" :description "Open a draft GitHub pull request."}]} ``` For the full packaged steps reference, see `REFERENCE_PACKAGED_STEPS.md` or [Packaged Steps](/docs/reference-packaged-steps). ## Using MCP Tools MCP tools are selected from top-level flow adapters. The remote endpoint is still a normal `:http-api` requirement with `:backend :mcp`; the adapter defines the MCP transport, allowlist, optional path, tool prefix, and known tool schemas. ```clojure {:requires [{:slot :linear-mcp :type :http-api :label "Linear MCP" :backend :mcp :base-url "https://mcp.linear.app" :auth {:type :bearer}}] :mcp [{:id :linear/issues :connection :linear-mcp :transport :streamable-http :allow-tools ["list_issues" "get_issue"] :tool-prefix "linear" :tools [{:name "list_issues" :description "List Linear issues." :input-schema {:type "object" :properties {:team_id {:type "string"}} :required ["team_id"]}}]}] :flow '(flow/step :agent :triage {:connection :ai :objective "Find the most relevant blocker issue." :tools {:mode :execute :mcp [:linear/list_issues]}})} ``` MCP tools use the same budgets, approval hooks, repeated-call limits, tracing, and provider tool-loop behavior as other agentic tools. Breyta does not expose all tools from an MCP endpoint by default; adapter-wide refs require `:allow-tools`, and individual tool refs publish only the named tool. The default 190-second MCP tool timeout gives headroom over Breyta MCP interfaces' 180-second synchronous wait; set adapter `:timeout-ms` when a remote MCP server has a tighter or longer expected response window. ## Installed Flow Behavior An installed flow is the **author's product**. Internal agent state belongs to the author, not the installer. The installer sees the flow's outputs, not its working memory or debug traces. | Feature | Belongs to | Why | | --- | --- | --- | | Memory | Author's workspace | Internal working state — observations, decisions, plan progress | | Cost ledger | Author's flow (KV) | The author's budget applies globally across all installations | | Trace | Author's workspace | The author debugs agent behavior, not the installer | | Evaluation | Author's workspace | Quality tracking is the author's concern | | Output / result | Installer's workspace | The product output the installer consumes | | Connections | Installer's bindings | LLM keys, GitHub tokens, etc. come from the installation | Memory tables, trace blobs, and evaluation entries are persisted through the flow's own resource context (the author's workspace). The installer never sees or manages these resources. Cost tracking is flow-scoped — the author sets a budget that applies globally across all installations, because the author is responsible for the LLM spend their flow generates. Output persistence (`:output-persist`) writes to the runtime workspace (the installer's) because that is the product output the installer paid for and needs access to. For a complete installable agent flow checklist and example, see the [Installable Agent Flows](/docs/guide-installations) section in the Installations guide. ## Related - [Step LLM](/docs/reference-step-llm) - [Step Files](/docs/reference-step-files) - [Step Table](/docs/reference-step-table) - [Packaged Steps](/docs/reference-packaged-steps) - [Step Reference](/docs/reference-step-reference) - [Installations (Installable Agent Flows)](/docs/guide-installations) - [Persisted Results And Resource Refs](/docs/guide-persisted-results-and-resources) --- Document: Step Breyta URL: https://flows.breyta.ai/docs/reference-step-breyta.md HTML: https://flows.breyta.ai/docs/reference-step-breyta Last updated: 2026-05-15T14:59:18+02:00 # Step Breyta (`:breyta`) ## Quick Answer Use `:breyta` to give an agent a narrow, allowlisted tool for reading and, when explicitly enabled, mutating Breyta workspace control-plane resources such as flows, runs, docs, and resources. ```clojure {:type :agent :objective "Inspect the latest failed run and summarize the likely fix." :tools {:mode :execute :breyta {:allow [:flows/list :runs/show :resources/read] :mutations :none}}} ``` The runtime owns workspace identity, actor identity, auth, and API routing. The model can only choose an allowed operation and operation-specific arguments. ## Canonical Agent Tool Config | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:allow` | vector | Yes | Default-deny operation allowlist. | | `:mutations` | keyword | No | One of `:none`, `:draft-only`, `:run-only`, `:write`. Defaults to `:none`. | | `:require-approval` | vector | No | Operations that trigger an agent approval request before execution. | ## Operations Read operations: - `:flows/list` - `:flows/show` - `:flows/pull` - `:runs/list` - `:runs/show` - `:resources/list` - `:resources/read` - `:docs/find` - `:docs/show` Mutation operations: - `:flows/push-draft` requires `:mutations :draft-only` or `:write` - `:flows/run` requires `:mutations :run-only` or `:write` - `:feedback/send` requires `:mutations :draft-only` or `:write` ## Operation Arguments Tool calls use this shape: ```clojure {:op :runs/show :args {:workflow-id "workflow-id-from-runs-list"}} ``` The `:args` map is specific to `:op`. - Use kebab-case keys in flow definitions. - JSON callers use the full operation string, for example `"flows/list"` or `"runs/show"`; do not shorten it to `"list"` or `"show"`. - Runtime-owned fields such as `:workspace-id`, `:actor`, `:auth`, `:token`, `:api-url`, `:allow`, and `:mutations` are stripped. - Published agent tool schemas expose operation-specific `:args` variants. | Operation | Required Args | Optional Args | | --- | --- | --- | | `:flows/list` | None | `:archived` boolean, `:include-archived` boolean, `:limit` integer up to 100, `:cursor` string | | `:flows/show` | `:flow-slug` string/keyword | `:source` `:active`/`:draft`/`:latest`, `:version` integer, `:include-flow-literal` boolean, `:include-templates` boolean, `:include-functions` boolean | | `:flows/pull` | `:flow-slug` string/keyword | Same optional args as `:flows/show`; intended for reading source/export details | | `:flows/push-draft` | `:flow-literal` string containing a complete flow definition | `:deploy-key` string | | `:flows/run` | `:flow-slug` string/keyword | `:input` map, `:installation-id` string, `:target` `:draft`/`:live`, `:source` `:active`/`:draft`/`:latest`, `:version` integer | | `:runs/list` | None | `:flow-slug` string/keyword, `:installation-id` string, `:version` integer, `:status` string/keyword, `:limit` integer up to 100, `:cursor` string | | `:runs/show` | `:workflow-id` string | `:include-steps` boolean, `:include-result` boolean | | `:resources/list` | None | `:type` string, `:prefix` string, `:tags` vector of strings, `:query` string, `:limit` integer, `:cursor` string, `:storage-backend` string, `:storage-root` string, `:path-prefix` string | | `:resources/read` | `:uri` string | `:format` `:json`/`:text`/`:raw` | | `:docs/find` | None | `:query` string, `:limit` integer, `:source` `:all`/`:public`/`:cli`, `:with-snippets` boolean | | `:docs/show` | `:slug` string | `:format` `:json`/`:markdown` | | `:feedback/send` | `:title` string, `:description` string | `:type` `:issue`/`:feature_request`/`:general`, `:source` `:agent`/`:human`/`:system`, `:tags` vector of strings, `:metadata` map, `:context` map, `:command` string, `:flow-slug` string/keyword, `:workflow-id` string, `:run-id` string | For `:flows/run`, use `:target :draft` to test an unreleased draft. Use `:target :live` or `:installation-id` when running a released/public installation target. Examples: ```clojure {:op :flows/list :args {:limit 20}} {:op :flows/show :args {:flow-slug :lead-research :source :draft :include-flow-literal true}} {:op :flows/run :args {:flow-slug :lead-research :target :draft :input {:company "Breyta"}}} {:op :resources/read :args {:uri "res://v1/ws/ws-acme/result/table/tbl_leads" :format :json}} ``` CLI JSON probe: ```bash breyta steps run --flow my-flow --source draft --type breyta \ --params '{"op":"flows/list","allow":["flows/list"],"args":{"limit":5}}' ``` ## Runtime Behavior - The workspace is always the current workflow runtime workspace. - Auth tokens, API base URLs, actor ids, and workspace ids are not accepted from tool-call arguments. - Tool-call arguments named like `workspace-id`, `auth`, `token`, `api-url`, `allow`, `mutations`, or `require-approval` are stripped before step execution. - Calls run through an activity-backed worker executor and reuse existing Breyta command, docs, and resource handlers. - Every successful or failed call can emit a redacted audit event when the runtime provides `:breyta-control-plane-audit-fn`. - Mutation activities use the single-attempt execution profile; expose mutations only with explicit idempotency in the called operation arguments. ## Direct Step Shape The direct step shape is available for non-agent orchestration, but most uses should expose it through `:agent` tools. ```clojure (flow/step :breyta :show-run {:op :runs/show :allow [:runs/show] :args {:workflow-id (:workflow-id (flow/input))}}) ``` ## Example With Approval ```clojure {:type :agent :objective "Inspect the draft and run it only after approval." :approval {:mode :all-mutations} :tools {:mode :execute :breyta {:allow [:flows/show :flows/run] :mutations :run-only :require-approval [:flows/run]}}} ``` ## Related - [Step Agent](/docs/reference-step-agent) - [Step LLM](/docs/reference-step-llm) - [Step Reference](/docs/reference-step-reference) --- Document: Step LLM URL: https://flows.breyta.ai/docs/reference-step-llm.md HTML: https://flows.breyta.ai/docs/reference-step-llm Last updated: 2026-05-21T16:29:37+02:00 # Step LLM (`:llm`) ## Quick Answer Use this reference for the `:llm` step schema, prompt/message patterns, templates, and model-call configuration. Use for model calls via an LLM-capable `:http-api` connection. ## Canonical Shape Core fields: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:type` | keyword | Yes | Must be `:llm` | | `:expect` | map | No | Optional output expectation/assertion metadata | | `:connection` | keyword/string | Recommended | Slot or connection id | | `:messages` | vector | Yes* | Explicit chat messages | | `:prompt` | string/map | Yes* | Prompt shorthand | | `:system` | string | No | System prompt shorthand | | `:input` | map | No | Canonical input envelope | | `:template` | keyword | No | `:llm-prompt` template id | | `:data` | map | No | Template data | | `:model` | string | No | Model override | | `:provider` | keyword/string | No | Provider override | | `:temperature`, `:top-p`, `:stop` | scalar | No | Generation controls | | `:max-tokens` | int | No | Response token cap | | `:seed`, `:presence-penalty`, `:frequency-penalty` | scalar | No | Provider-supported generation controls | | `:output` / `:response-format` | map/keyword | No | Structured output config; `:output :schema` accepts Malli schemas or raw JSON Schema maps | | `:json-schema` | map | No | Legacy raw JSON Schema field | | `:provider-opts` | map | No | Provider-specific escape hatch | | `:base-url`, `:deployment`, `:api-version` | string | No | Custom/Azure/Chat Completions-compatible endpoint overrides | | `:reasoning-effort` | keyword/string | No | Reasoning effort for providers that support it | | `:prompt-cache-key`, `:previous-response-id`, `:cache-system?` | scalar/boolean | No | Provider-specific prompt caching and response continuation controls | | `:tools` | map/vector | No | Agentic tools config; supports `:steps [...]` for packaged steps, `:agents [...]` for flow-level agent definitions, and `:mcp [...]` for flow-level MCP adapters | | `:openai` | map | No | OpenAI Responses-specific options (`:responses`) | | `:available-steps` | vector | No | Auto-tool step set | | `:max-iterations` | int | No | Agentic loop bound; default `20`, max `100` | | `:max-tool-calls` | int | No | Total executed tool-call cap; default `100`, max `1000` | | `:max-repeated-tool-calls` | int | No | Optional cap for identical tool name+arguments calls; unset by default, max `100` | | `:auth` | map | No | Explicit auth if not using connection | | `:workspace-id` | string | No | Runtime context override; normally supplied by Breyta | \* Provide `:messages`, `:prompt`/`:system`, or `:input`. ## Limits And Behavior - Use either `:messages` or `:prompt`/`:system`. - Prefer templates for long prompts. - `:tools` belongs on the step config, not inside templates. - Token cost estimates are derived from provider/model usage and platform token pricing, not from step `:metering`. See [Run Cost Estimates](/docs/guide-run-cost-estimates). - For most production flows, bind an LLM-capable connection via `:requires`. New stored connections should use `:http-api` with an LLM backend. - If a connection-create UI still exposes `:llm-provider`, treat it as a legacy alias only. The canonical stored connection and authored `:requires` type are both `:http-api`. - `:max-tokens` policy: - platform-managed keys are capped by platform limit - user-provided keys are not platform-clamped (provider/account limits still apply) - OpenAI hosted shell can be configured via `:openai {:responses ...}`. - Vision-capable models can receive uploaded or persisted image resources via multipart `:messages` content parts. See [Image Resource Inputs](#image-resource-inputs). - Supported providers for agentic tool execution (`:tools` with `:mode :execute`): - `:openai` — OpenAI Responses API (most feature-rich: reasoning effort, CoT, prompt caching, native tools) - `:anthropic` — Anthropic Messages API (tool calling, prompt caching) - `:bedrock` — AWS Bedrock Runtime for Anthropic Claude models, signed with AWS SigV4 - `:google` — Google Gemini API (tool calling) - `:deepseek` — DeepSeek Chat Completions API (tool calling, agentic loop, reasoning effort mapped to thinking mode) - `:openrouter` — OpenRouter Chat Completions API (tool calling, structured output, agentic multi-turn tool loop for compatible routed models) - `:chat-completions-compatible` — generic Chat Completions wire format (Groq, Together, Fireworks, Ollama, Azure, Mistral) — built-in family is conservative: tool calling in propose mode only, no default agentic multi-turn tool loop - `:openai-compatible` — legacy alias for `:chat-completions-compatible` - OpenRouter connections use `:backend :openrouter` with base URL `https://openrouter.ai/api/v1`. Model ids are OpenRouter model ids such as `"openai/gpt-4o-mini"` or `"google/gemini-2.5-flash"`. Optional OpenRouter attribution headers can be supplied through `:provider-opts`, for example `{:http-referer "https://example.com" :x-title "My Flow"}`. Breyta sends the title as `X-OpenRouter-Title`. - For OpenRouter reasoning models, Breyta keeps provider reasoning metadata separate from visible content and preserves it across tool turns when the provider returns replay details. - AWS Bedrock connections use `:backend :bedrock`, an AWS SigV4 auth config, and an Anthropic Claude Bedrock model id such as `"anthropic.claude-3-5-sonnet-20241022-v2:0"` or an inference profile id such as `"us.anthropic.claude-3-5-sonnet-20241022-v2:0"`. See [AWS Bedrock Claude](#aws-bedrock-claude). - Custom providers can be registered via `:providers {:llm [...]}` in the flow definition. See [LLM Providers](/docs/reference-flow-definition). - A verified Chat Completions-like endpoint can opt into agentic execution by defining a flow-level provider with `:family :chat-completions-compatible` and explicit `:capabilities #{:tool-calling :structured-output :agentic-tool-loop}`. This is author-owned compatibility: the endpoint must accept assistant `tool_calls` replay followed by `role=tool` messages, keep tool call ids stable, and support the selected model's tool-calling behavior. - When `:tools {:steps [...]}` lists qualified packaged step ids, those flow-local packaged steps are also published as tools alongside built-in step tools. See [Packaged Steps](/docs/reference-packaged-steps). - When `:tools {:mcp [...]}` lists MCP adapter ids or tool refs, selected remote MCP tools are published as ordinary agentic tools. MCP endpoints are bound through `:http-api` requirements with `:backend :mcp`; see [Flow Definition — MCP Tool Adapters](/docs/reference-flow-definition#mcp-tool-adapters). - Agentic loop defaults: - `:max-iterations` defaults to `20` and may be set up to `100`. - `:max-tool-calls` defaults to `100` and may be set up to `1000`. - `:max-repeated-tool-calls` is unset by default; set it to stop repeated identical tool calls. - When a value is present both top-level and inside `:tools`, the `:tools` envelope value wins for that run. - Current limitations: - `:openai {:responses {:transport :websocket}}` is not supported. - `:openai {:responses {:shell {:environment {:type :local}}}}` is not supported. - OpenAI Responses-only options (`:previous-response-id`, `:openai.responses`) are only available with the `:openai` provider. - `:reasoning-effort` is available with providers that declare it, currently `:openai` and `:deepseek`. ## Canonical Example ```clojure ;; In the flow definition: ;; :templates [{:id :summary ;; :type :llm-prompt ;; :system "You are concise." ;; :prompt "Summarize in 3 bullets:\\n{{text}}"}] ;; :functions [{:id :llm-input ;; :language :clojure ;; :code "(fn [input] {:text (:text input)})"}] '(let [prepared (flow/step :function :prepare-llm-input {:ref :llm-input :input (flow/input)}) result (flow/step :llm :summarize {:connection :ai :model "gpt-4o-mini" :template :summary :data prepared :output {:format :json} :tools {:mode :propose :allowed ["files" "table" "search"]} :max-iterations 20})] result) ``` ## MCP Tools Use MCP tools when a remote MCP endpoint already exposes the operations you want the model to call. The LLM step does not bind directly to a remote server; it selects from top-level `:mcp` adapters. ```clojure ;; Flow-level setup: {:requires [{:slot :linear-mcp :type :http-api :label "Linear MCP" :backend :mcp :base-url "https://mcp.linear.app" :auth {:type :bearer}}] :mcp [{:id :linear/issues :connection :linear-mcp :transport :streamable-http :allow-tools ["list_issues"] :tool-prefix "linear" :tools [{:name "list_issues" :description "List Linear issues." :input-schema {:type "object" :properties {:team_id {:type "string"}} :required ["team_id"]}}]}]} ;; Step-level selection: (flow/step :llm :find-issues {:connection :ai :prompt "Find current blocker issues for team ENG." :tools {:mode :execute :mcp [:linear/list_issues]}}) ``` The model sees a sanitized tool name such as `linear_list_issues`. Runtime arguments are sent to the MCP server as JSON-RPC `tools/call` over the existing HTTP activity path, so connection auth, SSRF checks, timeout, and response-size limits apply. The default 190-second MCP tool timeout gives headroom over Breyta MCP interfaces' 180-second synchronous wait; set adapter `:timeout-ms` when a remote MCP server has a tighter or longer expected response window. ## AWS Bedrock Claude For Amazon Bedrock Claude model calls, bind the LLM step to a canonical `:http-api` connection with `:backend :bedrock`. The connection stores the Bedrock Runtime base URL and an AWS SigV4 auth config; the secret stores AWS credentials. Requirement shape: ```clojure {: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"}}]} ``` Secret payload: ```json { "access-key-id": "AKIA...", "secret-access-key": "...", "session-token": "optional temporary credential token" } ``` Step shape: ```clojure (flow/step :llm :extract {:connection :ai :model "anthropic.claude-3-5-sonnet-20241022-v2:0" :system "Extract the key facts." :prompt "Use the attached report and return JSON." :messages [{:role "user" :content [{:type "text" :text "Summarize this image."} {:type :image-resource :uri (:uri (:uploaded-image (flow/input)))} {:type "text" :text "Return the result as JSON."}]}] :output {:format :json}}) ``` Notes: - The `:llm` Bedrock path targets Bedrock Runtime `InvokeModel` and sends the Anthropic Messages request shape with `anthropic_version "bedrock-2023-05-31"`. - `:service` should be `"bedrock"` for Bedrock Runtime. - The hosted Breyta Bedrock backend currently supports AWS SigV4 auth. Amazon Bedrock API keys are a separate bearer-token auth surface and are not wired through this backend yet; do not configure Bedrock as `:api-key` or `:bearer`. - For Bedrock Claude Sonnet 4.5 and Haiku 4.5 model ids, set either `:temperature` or `:top-p`, not both. - Uploaded or persisted image resources can be used in `:messages` content parts for Bedrock Claude vision-capable models with `:type :image-resource`. Breyta reads the resource and sends the Bedrock-supported base64 image block; do not pass `res://...` values as raw `image_url` URLs. - AWS Bedrock's Claude Messages API documents image input as base64 image bytes, while direct Anthropic API also supports image URLs. For Bedrock, use `:image-resource` or an explicit data URL rather than an HTTPS image URL. - Use [Step HTTP — AWS SigV4](/docs/reference-step-http#auth-variant-aws-sigv4-aws-sigv4) for raw Bedrock Runtime operations that are not normal Claude message calls. ## Full Config Example This example covers the complete authored configuration surface. Prefer the smaller canonical example unless you need the specific option. ```clojure (flow/step :llm :review {:connection :ai :provider :openai :model "gpt-5.2" :expect {:contains ["summary"]} ;; Input forms. Use one primary form in real flows. :input {:system "You are a precise reviewer." :prompt "Review {{topic}}." :context {:topic "billing"}} :messages [{:role "system" :content "You are a precise reviewer."} {:role "user" :content [{:type "text" :text "Review this screenshot."} {:type "image_url" :image_url {:url "https://example.com/screen.png" :detail "high"}}]}] :system "You are concise." :prompt "Summarize {{topic}}." :template :summary :data {:topic "billing"} ;; Generation controls. :temperature 0.2 :top-p 0.9 :stop ["\nDONE"] :max-tokens 2000 :seed 42 :presence-penalty 0.0 :frequency-penalty 0.1 ;; Structured output. Prefer Malli schemas in :output :schema. :output {:format :json :schema [:map [:summary :string] [:confidence :double]] :style :deterministic :strict? true} ;; Legacy compatibility fields. Omit these when using :output above unless ;; you intentionally want the top-level values to override canonical output. :response-format :json :json-schema {"type" "object"} ;; Provider and endpoint controls. :provider-opts {:openai {:responses {:store false}}} :base-url "https://api.openai.com/v1" :deployment "prod-reviewer" :api-version "2025-04-01-preview" :reasoning-effort :medium :prompt-cache-key "summary-v1" :previous-response-id "resp_previous" :cache-system? true :openai {:responses {:tool-choice "required" :store false}} ;; Tool calling. :available-steps [:files :table :search] :tools {:mode :execute :allowed ["files" "table" "search"] :steps [:github/open-pr] :agents [:review/security] :require {:tool-names ["files"] :steps [:github/open-pr] :agents [:review/security]} :definitions {:custom_tool {:name "custom_tool" :type :files}} :max-iterations 12 :max-tool-calls 80 :max-repeated-tool-calls 3} :max-iterations 20 :max-tool-calls 100 :max-repeated-tool-calls 4 ;; Usually prefer :connection over explicit :auth. :auth {:type :api-key :header "Authorization" :prefix "Bearer"} :workspace-id "ws-runtime-override"}) ``` ## Structured Output Schemas For new flows, prefer the canonical `:output` envelope with a Malli schema: ```clojure (flow/step :llm :extract {:connection :ai :prompt "Extract the support request." :output {:format :json :schema [:map [:subject :string] [:priority [:enum "low" "normal" "high"]] [:tags [:vector :string]]]}}) ``` Breyta validates the Malli schema form, converts it to provider-facing JSON Schema before the model call, and keeps provider-specific schema capabilities behind the normal LLM provider boundary. Raw JSON Schema maps are still accepted and are passed through unchanged: ```clojure :output {:format :json :schema {"type" "object" "properties" {"subject" {"type" "string"}}}} ``` The older top-level `:response-format` and `:json-schema` fields remain supported for existing flows. Prefer `:output {:format :json :schema ...}` for new authoring because the same shape works for both `:llm` and `:agent`. If both forms are present, the top-level legacy fields win for backward compatibility. DeepSeek supports JSON mode with `:output {:format :json}`, but the standard DeepSeek chat endpoint does not accept Breyta JSON Schema structured output. Use JSON mode without `:schema`, or add a deterministic validation/repair step after the DeepSeek call. ## Image Resource Inputs Use `:image-resource` content parts when a flow input or earlier step produced an uploaded/persisted image resource. Breyta resolves these only through the workspace resource system; the `:llm` step does not fetch arbitrary remote image URLs. If the image starts on the web, first use an explicit resource-producing step such as `:http`/`:files`/`:persist`. ```clojure (flow/step :llm :describe-screenshot {:connection :ai :model "gpt-4o" :input {:messages [{:role "user" :content [{:type :text :text "Describe this screenshot."} {:type :image-resource :resource uploaded-image-ref :detail :high}]}]}}) ``` The resource locator can be supplied as any of: - `:resource uploaded-image-ref` - `:uri "res://..."` - `:res-uri "res://..."` - `:resource-uri "res://..."` Optional image fields: | Field | Type | Notes | | --- | --- | --- | | `:detail` | `:auto`, `:low`, `:high` | Provider detail hint; defaults to `:auto` | | `:transport` | `:url`, `:data-url` | `:url` requires signed URL support; `:data-url` forces worker-side inline transport | | `:signed-url-ttl-seconds` / `:url-ttl-seconds` | int | Signed URL TTL, clamped to 60-3600 seconds | | `:processing` | map | Optional downsample/re-encode before provider call | Processing options: ```clojure {:type :image-resource :resource uploaded-image-ref :transport :data-url :processing {:max-width 1600 :max-height 1600 :format :jpeg :quality 85}} ``` Behavior and guardrails: - Image resources require a vision-capable provider/model. Unsupported models fail before the provider HTTP request. - Resource reads stay inside the existing workspace resource/storage boundary. - Data URL transport validates declared content type, checks known image byte signatures, enforces encoded byte and decoded pixel limits, and re-encodes decoded images when processing is requested. - When the provider/model supports URL image transport and no `:processing` is requested, Breyta prefers a short-lived signed storage URL to keep the provider request smaller. - `:transport :url` is strict: if an HTTPS signed URL cannot be prepared, the step fails instead of silently falling back. - Signed URLs and base64 image data are not returned in step outputs and should not be logged by authored flow code. ## OpenAI Responses Hosted Web Search OpenAI Responses supports hosted web-search tools. In Breyta, pass the native Responses tool config through `:openai.responses.tools` on an OpenAI-backed `:llm` or `:agent` step: ```clojure '(flow/step :llm :research {:connection :ai :model "gpt-5.4" :prompt "Search for the latest official pricing page and summarize only cited facts." :openai {:responses {:tools [{:type "web_search"}] :tool-choice "auto" :store false}}}) ``` Provider naming can vary by OpenAI Responses API version and model. Use the exact tool type supported by the provider, commonly `web_search` or `web_search_preview`. This is different from Breyta's built-in `:search` step tool and different from packaged/MCP/Breyta tools configured through `:tools {:steps [...]}`, `:tools {:mcp [...]}`, or `:tools {:breyta ...}`. Reference: OpenAI web search guide: `https://platform.openai.com/docs/guides/tools-web-search?api-mode=responses` ## OpenAI Responses Hosted Shell ```clojure '(flow/step :llm :support-agent {:connection :ai :model "gpt-5.2" :prompt "Analyze this support email and draft a reply." :openai {:responses {:shell {:environment {:type :container_auto :network-policy {:type :allowlist :allowed-domains ["gmail.googleapis.com"]}}} :tool-choice "required" :store false}}}) ``` ## Connection And Installation Notes For installable flows, declare the LLM connection requirement in `:requires` so the binding is resolved at install time: ```clojure ;; Author provides the connection (installer never sees an API key): {:requires [{:slot :ai :type :http-api :provided-by :author}]} ;; Installer provides the connection (installer enters their own key): {:requires [{:slot :ai :type :http-api :label "LLM Provider" :auth {:type :api-key}}]} ``` Then reference the slot in the step config: ```clojure (flow/step :llm :summarize {:connection :ai :prompt "Summarize this."}) ``` When the flow is installed, the platform resolves `:connection :ai` through the selected installation binding. The step never hardcodes credentials. DeepSeek connections should use a normal HTTP API requirement with the DeepSeek backend: ```clojure {:requires [{:slot :deepseek-api :type :http-api :label "DeepSeek" :base-url "https://api.deepseek.com" :backends #{:deepseek} :auth {:type :api-key}}]} (flow/step :llm :discover {:connection :deepseek-api :provider :deepseek :model "deepseek-v4-pro" :prompt "Find three candidate companies." :tools {:mode :execute :steps [:web/search]}}) ``` OpenRouter connections use the same HTTP API requirement shape with the OpenRouter backend. Tool and structured-output support depends on the routed model, so pick OpenRouter models that advertise the features your flow needs. ```clojure {:requires [{:slot :openrouter-api :type :http-api :label "OpenRouter" :base-url "https://openrouter.ai/api/v1" :backends #{:openrouter} :auth {:type :api-key}}]} (flow/step :llm :delegate-review {:connection :openrouter-api :provider :openrouter :model "deepseek/deepseek-v4-flash" :provider-opts {:http-referer "https://breyta.ai" :x-title "Breyta Flow"} :prompt "Review this repository and ask both specialist agents for input." :tools {:mode :execute :agents [:review/deepseek :review/opus] :require {:agents [:review/deepseek :review/opus]} :max-iterations 5}}) ``` Use `:provided-by :author` when the author wants to absorb LLM costs across all installations. Use installer-provided when each installer should bring their own API key and control their own spend. ## Related - [Step Agent](/docs/reference-step-agent) — objective/input-oriented wrapper over the `:llm` tool loop - [Packaged Steps](/docs/reference-packaged-steps) — flow-local step wrappers publishable as agent tools - [Step Files](/docs/reference-step-files) — source-tree and changeset operations - [Step Table](/docs/reference-step-table) — table-resource operations - [Installations](/docs/guide-installations) — installable agent flows - [Flow Definition](/docs/reference-flow-definition) - [Templates](/docs/reference-templates) - [Limits And Recovery](/docs/reference-limits-and-recovery) - [CLI Commands](/docs/reference-cli-commands) --- Document: Step DB URL: https://flows.breyta.ai/docs/reference-step-db.md HTML: https://flows.breyta.ai/docs/reference-step-db Last updated: 2026-05-15T14:59:18+02:00 # Step DB (`:db`) ## Quick Answer Use `flow/step :db` with a required `:database` discriminator and a bound `:connection`. Canonical backends are `:postgres`, `:mysql`, `:clickhouse`, `:bigquery`, and `:firestore`. ## Canonical Shape Common fields: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:database` | keyword/string | Yes | Backend discriminator | | `:connection` | keyword/string | Yes | Bound slot or connection id | | `:max-results` | int | No | SQL/BigQuery max rows (platform capped) | | `:timeout` | int | No | Query timeout seconds | | `:persist` | map | No | Persist large results as `res://` refs | Backend-specific fields: | Backend | Required fields | Optional fields | Notes | | --- | --- | --- | --- | | `:postgres`, `:mysql`, `:clickhouse` | `:sql` or `:template` | `:params` (vector), `:timeout`, `:max-results` | Positional params | | `:bigquery` | `:sql` or `:template` | `:params` (map), `:dry-run`, `:timeout`, `:max-results` | Named params (`@param`) | | `:firestore` | `:collection` | `:where`, `:order-by`, `:limit` | Collection query API (not SQL) | ## Limits And Behavior - SQL/BigQuery row cap: up to `5000` rows (`:max-results`) - Firestore default `:limit`: `100`, max `1000` - Result payload budget: `1 MB` per DB step result - For large/variable outputs, prefer `:persist` ## Connection Reuse Pattern Define database requirements once in `:requires`, then reuse the slot in all DB steps: ```clojure {:requires [{:slot :warehouse :type :database :backends #{:postgres} :label "Warehouse DB"}] :functions [{:id :db-input :language :clojure :code "(fn [input] {:status (or (:status input) \"active\")})"}] :flow '(let [prepared (flow/step :function :prepare-db-input {:ref :db-input :input (flow/input)})] (flow/step :db :query {:database :postgres :connection :warehouse :sql "select id from users where status = ?" :params [(:status prepared)]}))} ``` Use existing workspace connections before creating new ones: ```bash breyta connections list --type database breyta flows configure --set warehouse.conn=conn-... ``` ## Backend Auth And Binding Expectations | Backend family | Recommended `:requires` | Connection expectation | | --- | --- | --- | | SQL (`:postgres`, `:mysql`, `:clickhouse`) | `{:type :database :backends #{...}}` | `config.connection` points to a secret id containing a JDBC URL | | BigQuery (`:bigquery`) | `{:type :database :backends #{:bigquery}}` | GCP project + service-account auth in connection | | Firestore (`:firestore`) | `{:type :database :backends #{:firestore}}` | GCP project + service-account auth in connection | SQL secret values must be JDBC URLs, for example `jdbc:postgresql://host:5432/db?user=...&password=...`. ## Canonical Example (SQL + Template + Function) ```clojure {:templates [{:id :orders-since :type :db-query :sql "select id, amount, created_at from orders where created_at >= ? order by created_at desc"}] :functions [{:id :orders-stats :language :clojure :code "(fn [rows] {:rows (count rows) :total-amount (reduce + 0 (map second rows))})"}] :flow '(let [rows (flow/step :db :fetch-orders {:database :postgres :connection :warehouse :template :orders-since :params ["2026-01-01"] :max-results 1000}) stats (flow/step :function :summarize-orders {:ref :orders-stats :input rows})] {:stats stats})} ``` ## Related - [Step DB SQL](/docs/reference-step-db-sql) - [Step DB BigQuery](/docs/reference-step-db-bigquery) - [Step DB Firestore](/docs/reference-step-db-firestore) - [Flow Configuration](/docs/guide-flow-configuration) - [Flow Configuration](/docs/guide-flow-configuration) - [Start Here](/docs/start-here) - [Flow Authoring](/docs/build-flow-authoring) - [Templates](/docs/reference-templates) - [Limits And Recovery](/docs/reference-limits-and-recovery) --- Document: Step DB SQL URL: https://flows.breyta.ai/docs/reference-step-db-sql.md HTML: https://flows.breyta.ai/docs/reference-step-db-sql Last updated: 2026-05-15T14:59:18+02:00 # Step DB SQL ## Quick Answer Use `:db` with `:database` set to `:postgres`, `:mysql`, or `:clickhouse`. Provide either `:sql` or `:template` (not both), and pass values through `:params` as a vector. ## Canonical Shape | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:database` | keyword/string | Yes | `:postgres`, `:mysql`, or `:clickhouse` | | `:connection` | keyword/string | Yes | Bound database slot or connection id | | `:sql` | string | Yes* | Inline SQL | | `:template` | keyword | Yes* | `:db-query` template id | | `:params` | vector | No | Positional parameters for placeholders | | `:timeout` | int | No | Query timeout in seconds | | `:max-results` | int | No | Row cap (up to platform max) | \* Provide exactly one of `:sql` or `:template`. ## Canonical Example (Template + Positional Params) ```clojure {:templates [{:id :users-by-status :type :db-query :sql "select id, email from users where status = ? order by id asc limit ?"}] :functions [{:id :to-status-input :language :clojure :code "(fn [input] [(:status input) (or (:limit input) 100)])"}] :flow '(let [input (flow/input) params (flow/step :function :build-params {:ref :to-status-input :input input}) rows (flow/step :db :fetch-users {:database :postgres :connection :app-db :template :users-by-status :params params :max-results 1000})] {:count (count rows) :rows rows})} ``` ## Parameterization And Safety - Prefer parameterized SQL with `:params` - Avoid building query values with string concatenation - Keep SQL in top-level `:templates` once query complexity grows ## Connection And Binding Recipe ```clojure {:requires [{:slot :warehouse :type :database :backends #{:postgres} :label "Warehouse SQL DB"}]} ``` ```bash breyta connections list --type database breyta flows configure --set warehouse.conn=conn-... ``` For SQL connections, the connection config's `connection` value should be the secret id that stores the JDBC URL. The secret value must start with the JDBC prefix for the backend, such as `jdbc:postgresql://` for Postgres. ```bash breyta connections update conn-... --config '{"connection":"warehouse-jdbc-url"}' breyta flows configure --set 'warehouse-jdbc-url.secret=jdbc:postgresql://localhost:5432/postgres?user=postgres&password=postgres' breyta connections test conn-... breyta flows configure check ``` Use `jdbc:postgresql://...`, not `postgresql://...`. ## Parameter Pitfalls - `:params` must match placeholder order exactly - prefer `?` placeholders for portable JDBC SQL - for dynamic query shaping, build params in a function step and keep SQL static in templates - inline string literals in SQL can be blocked by query security checks, so keep user values in `:params` ## Query Design Checklist - project only needed columns - always pair `LIMIT` with deterministic `ORDER BY` - push filtering and aggregation into SQL before returning rows to flow memory - for variable-size result sets, return compact rows and then persist artifacts when needed ## Failure Triage - permission/auth failures: verify bound connection credentials and DB grants - connection errors: verify host/network reachability and DB availability - syntax/placeholder failures: check placeholder count and SQL syntax together - timeout/slow queries: add indexes, reduce scanned rows, and tune `:timeout` only after query shape is improved ## Output Shape SQL DB steps return rows as vectors. Use a function step to map or enrich structure for downstream steps. ## Related - [Step DB](/docs/reference-step-db) - [Step DB BigQuery](/docs/reference-step-db-bigquery) - [Step DB Firestore](/docs/reference-step-db-firestore) - [Templates](/docs/reference-templates) - [Flow Configuration](/docs/guide-flow-configuration) - [Limits And Recovery](/docs/reference-limits-and-recovery) --- Document: Step DB BigQuery URL: https://flows.breyta.ai/docs/reference-step-db-bigquery.md HTML: https://flows.breyta.ai/docs/reference-step-db-bigquery Last updated: 2026-02-16T17:24:13+01:00 # Step DB BigQuery ## Quick Answer Use `:db` with `:database :bigquery`, a bound `:connection`, and either `:sql` or `:template`. Use named parameters in `:params` (map) and `@name` placeholders in SQL. ## Canonical Shape | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:database` | keyword/string | Yes | Must be `:bigquery` | | `:connection` | keyword/string | Yes | Bound BigQuery connection | | `:sql` | string | Yes* | Inline SQL | | `:template` | keyword | Yes* | `:db-query` template id | | `:params` | map | No | Named query params (`{:date "2026-02-01"}`) | | `:dry-run` | boolean | No | Validate and estimate without executing | | `:timeout` | int | No | Query timeout in seconds | | `:max-results` | int | No | Row cap (up to platform max) | \* Provide exactly one of `:sql` or `:template`. ## Canonical Example (Template + Named Params) ```clojure {:templates [{:id :daily-usage :type :db-query :sql "select workspace_id, sum(tokens) as total_tokens from `billing.usage` where usage_date = @usage_date group by workspace_id"}] :functions [{:id :to-bigquery-params :language :clojure :code "(fn [input] {:usage_date (:usage-date input)})"}] :flow '(let [params (flow/step :function :build-params {:ref :to-bigquery-params :input (flow/input)}) rows (flow/step :db :query-usage {:database :bigquery :connection :billing-bq :template :daily-usage :params params :max-results 2000})] {:rows rows :row-count (count rows)})} ``` ## Limits And Behavior - Prefer templates for longer SQL and easier review - Use named params instead of inline literals - `:dry-run true` is useful during authoring to validate shape and cost before full execution ## Connection And Binding Recipe ```clojure {:requires [{:slot :billing-bq :type :database :backends #{:bigquery} :label "Billing BigQuery"}]} ``` ```bash breyta connections list --type database breyta flows configure --set billing-bq.conn=conn-... ``` ## Parameter Pitfalls - use `:params` as a map (named params), not a positional vector - query placeholder names must match map keys (`@usage_date` <-> `{:usage_date ...}`) - avoid inline literals for dynamic values; keep values in `:params` - prepare params via a function step so your SQL templates stay static and reusable ## Query And Cost Design Checklist - filter by partition/date early to reduce bytes scanned - aggregate in BigQuery before returning to flows - use `:dry-run true` during authoring to validate and estimate cost - cap result volume with `:max-results`, then persist downstream artifacts instead of returning large row sets ## Failure Triage - permission/auth failures: verify service account roles and project access - project/config failures: ensure bound connection has the target GCP project - syntax/params failures: verify `@param` usage and map keys - timeouts/slow queries: reduce scan size first, then tune timeout ## Output Shape BigQuery DB steps return rows as vectors in flow output. ## Related - [Step DB](/docs/reference-step-db) - [Step DB SQL](/docs/reference-step-db-sql) - [Templates](/docs/reference-templates) - [Limits And Recovery](/docs/reference-limits-and-recovery) - [Flow Configuration](/docs/guide-flow-configuration) --- Document: Step DB Firestore URL: https://flows.breyta.ai/docs/reference-step-db-firestore.md HTML: https://flows.breyta.ai/docs/reference-step-db-firestore Last updated: 2026-02-16T17:24:13+01:00 # Step DB Firestore ## Quick Answer Use `:db` with `:database :firestore`, a bound `:connection`, and Firestore query fields (`:collection`, `:where`, `:order-by`, `:limit`). Firestore DB queries are collection-based, not SQL-based. ## Canonical Shape | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:database` | keyword/string | Yes | Must be `:firestore` | | `:connection` | keyword/string | Yes | Bound Firestore connection | | `:collection` | keyword/string | Yes | Collection to query | | `:where` | vector | No | Clauses: `[field op value]` | | `:order-by` | vector | No | Clauses: `[field :asc/:desc]` | | `:limit` | int | No | Default `100`, max `1000` | ## Supported `:where` Operators - `:=` - `:!=` - `:<`, `:<=`, `:>`, `:>=` - `:in` - `:contains` - `:contains-any` ## Canonical Example ```clojure {:functions [{:id :rollup-usage :language :clojure :code "(fn [docs] {:count (count docs) :total-units (reduce + 0 (map #(or (:units %) 0) docs))})"}] :flow '(let [input (flow/input) docs (flow/step :db :fetch-metering-events {:database :firestore :connection :billing-firestore :collection :metering-events :where [[:workspace-id := (:workspace-id input)] [:period := (:period input)] [:status := :finalized]] :order-by [[:event-ts :asc]] :limit 1000}) summary (flow/step :function :summarize-events {:ref :rollup-usage :input docs})] {:summary summary :documents docs})} ``` ## Limits And Behavior - Use deterministic filters and ordering for repeatable queries - Keep result sets bounded; use `:limit` and narrow predicates - Firestore pagination by offset is not supported in this step shape ## Connection And Binding Recipe ```clojure {:requires [{:slot :billing-firestore :type :database :backends #{:firestore} :label "Billing Firestore"}]} ``` ```bash breyta connections list --type database breyta flows configure --set billing-firestore.conn=conn-... ``` ## Query And Index Design Checklist - use equality filters for tenant/workspace partitioning first - keep `:order-by` aligned with your range/sort access pattern - keep `:limit` explicit and bounded (default is `100`) - expect composite index requirements for more complex filter/order combinations - for high-volume reads, emit compact summaries in function steps before returning flow outputs ## Data And Filter Pitfalls - values in `:where` are normalized for Firestore compatibility (keywords become strings) - make sure filter value types match stored document field types - avoid unbounded collection scans; combine selective predicates with bounded `:limit` - Firestore offset pagination is not supported in this step shape ## Failure Triage - missing collection/config: verify `:collection` and bound connection - permission/auth failures: verify service account access to the project/database - large result errors: tighten `:where`, lower `:limit`, and persist downstream artifacts instead of returning full docs ## Output Shape Firestore DB steps return maps (documents) and include document `:id`. ## Related - [Step DB](/docs/reference-step-db) - [Step DB SQL](/docs/reference-step-db-sql) - [Templates](/docs/reference-templates) - [Persisted Results And Resources](/docs/guide-persisted-results-and-resources) - [Flow Configuration](/docs/guide-flow-configuration) --- Document: Step Wait URL: https://flows.breyta.ai/docs/reference-step-wait.md HTML: https://flows.breyta.ai/docs/reference-step-wait Last updated: 2026-05-10T21:06:10+02:00 # Step Wait (`:wait`) ## Quick Answer Use this reference for the `:wait` step schema, timeout semantics, and human/webhook resume patterns. Use for webhook/event waits or human-in-the-loop pauses. ## Canonical Shape Core fields: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:key` | string/keyword | Yes | Correlation key | | `:timeout` | string/number | No | Duration string (e.g. `"24h"`) or seconds | | `:on-timeout` | keyword/string | No | `:fail` (default) or `:continue` | | `:default-value` | any | No | Return value when timeout continues | | `:notify` | map | No | Notification config (channel `:http`) | | `:webhook` | map | No | Webhook auth/path config | | `:event` | map | No | Event routing config (`:name`, optional key path) | | `:roles` | vector | No | Role-based completion control | | `:validation` | map | No | Required fields on completion payload | ## Limits And Behavior - Use a stable `:key` so external events can resume the flow. - Webhook waits are resumed by external callback/event routes; waits pause execution. - `:notify` is optional and depends on your workspace notification setup. - For incoming webhooks, bind secret slots (`:type :secret`) through target bindings to secure requests. - When `:notify` is present, the wait record includes approval URL templates you can render and share. - Email notifications use the same `:http` channel and an email API connection (for example SendGrid). - `:on-timeout :continue` should typically be paired with `:default-value` to keep downstream branches deterministic. `:notify` fields (example shape): - `:channels` for per-channel configs (e.g. `{:http {:connection :notify-api :path "/notify" :method :post}}`) - HTTP notifications require a connection slot; inline auth is not supported. ## Canonical Examples Minimal wait: ```clojure (flow/step :wait :webhook {:key "approval" :timeout "24h" :notify {:channels {:http {:connection :notify-api :path "/notify" :method :post}}}}) ``` Email notification (SendGrid): ```clojure (flow/step :wait :approval {:key "approval-123" :timeout "24h" :notify {:channels {:http {:connection :sendgrid :path "/mail/send" :method :post :json {:personalizations [{:to [{:email "you@company.com"}]}] :from {:email "noreply@company.com" :name "Breyta"} :subject "Approval needed" :content [{:type "text/plain" :value "Approve: {{approvalUrl}}\nReject: {{rejectionUrl}}"}]}}}}}) ``` ## Step-by-step: approval URL templates Use this when a human must approve before the flow continues. 1) Add a wait step: ```clojure (flow/step :wait :approval {:key "approval-123" :timeout "10m" :notify {:channels {:http {:connection :notify-api :path "/notify" :method :post}}}}) ``` Expected template output (from wait list): ```edn {:approval {:template "{base-url}/{workspace-id}/waits/{wait-id}/{action}?token={token}" :params {:base-url "https://flows.breyta.ai" :workspace-id "ws_123" :wait-id "wait_abc" :token "token_..."} :actions [:approve :reject]}} ``` 2) Start a run and list waits: ```bash breyta flows run --input '{"n":41}' breyta waits list --flow ``` 3) Use the approval URL from the wait list output: - Open `approvalUrl` (or build from the template). - Approve or reject to resume the run. Example wait list payload (approval URL template): ```edn {:approval {:template "{base-url}/{workspace-id}/waits/{wait-id}/{action}?token={token}" :params {:workspace-id "ws_123" :wait-id "wait_abc" :token "token_..."} :actions [:approve :reject]}} ``` If you only need a single link, use `approvalUrl` or `rejectionUrl` from the wait list output. These routes redirect to login when unauthenticated and then resume the action. For notifications, `approvalUrl` and `rejectionUrl` are available as template data and should be referenced as `{{approvalUrl}}` and `{{rejectionUrl}}` in channel payload templates. ## Related - [Start Here](/docs/start-here) - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [Waits, Signals, And Timeouts](/docs/guide-waits-signals-and-timeouts) - [Notifications And Email](/docs/guide-notifications-and-email) - [CLI Commands](/docs/reference-cli-commands) --- Document: Step Function URL: https://flows.breyta.ai/docs/reference-step-function.md HTML: https://flows.breyta.ai/docs/reference-step-function Last updated: 2026-05-15T14:59:18+02:00 # Step Function (`:function`) ## Quick Answer Use this reference for the `:function` step schema, deterministic transform rules, and allowed helper/interop surface. Use for sandboxed transforms. ## Canonical Shape Core fields: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:type` | keyword | Yes | Use `:function` (`:code` alias is deprecated) | | `:input` | map | No | Input payload map; omitted means `{}` | | `:code` | form/string | Yes* | Inline function body | | `:ref` | keyword | Yes* | Reference top-level `:functions` entry | | `:load` | vector | No | Input keys to load from blob refs before execution | | `:persist` | map | No | Persist function output as retained `:blob`, `:kv`, or `:table` | \* Provide exactly one of `:code` or `:ref`. ## Limits And Behavior - Prefer `:ref` for reuse and readability. - Keep reusable logic in top-level `:functions`; keep step-level `:flow` focused on orchestration. - Function steps support common output persistence with `:persist`; top-level `:functions` entries are reusable code definitions and do not persist by themselves. - Function persists use the retained/default path today. For temporary large HTTP responses, persist the producing `:http` step as ephemeral, pass the ref, and add `:load` only where hydrated content is needed. - Function code always receives one input map argument. If `:input` is omitted, that argument starts as `{}` and still includes runtime context keys such as `:step/ctx` and `:flow/ctx`. - Keep functions deterministic; avoid time, randomness, and I/O. - If `:flow` validation says it cannot resolve `Long/parseLong` or similar parser/helper calls, move that logic into a `:function` step or top-level `:functions` entry. Regular flow-body SCI does not expose Java interop or the helper namespace documented here. - Safe helpers are exposed under `breyta.sandbox` (preferred; pure/deterministic): - `base64-encode` `(string|bytes) -> string` (Base64) - `base64-decode` `(string|bytes) -> string` (UTF-8) - `base64-decode-bytes` `(string|bytes) -> bytes` - `hex-encode` `(string|bytes) -> string` - `hex-decode` `(string) -> string` (UTF-8) - `hex-decode-bytes` `(string) -> bytes` - `sha256-hex` `(string|bytes) -> string` (hex digest) - `hmac-sha256-hex` `(key string|bytes, value string|bytes) -> string` (hex digest) - `uuid-from` `(string) -> uuid` - `uuid-from-bytes` `(string|bytes) -> uuid` - `parse-instant` `(string) -> java.time.Instant` (ISO-8601) - `format-instant` `(Instant) -> string` (ISO-8601) - `format-instant-pattern` `(Instant, pattern) -> string` (UTC) - `instant->epoch-ms` `(Instant) -> long` - `epoch-ms->instant` `(long) -> Instant` - `duration-between` `(Instant, Instant) -> Duration` - `truncate-instant` `(Instant, unit) -> Instant` (unit: `:seconds|:minutes|:hours|:days`) - `instant-plus` `(Instant, amount, unit) -> Instant` (unit: `:millis|:seconds|:minutes|:hours|:days`) - `instant-minus` `(Instant, amount, unit) -> Instant` - `url-encode` `(string) -> string` (UTF-8) - `url-decode` `(string) -> string` (UTF-8) - Safe JSON helpers are exposed under `json`: - `json/parse` `(string|bytes) -> data` with safe identifier-like JSON object keys keywordized by default; it takes exactly one argument, so do not pass parser option flags - `json/write-str` `(data) -> string` - keys outside the conservative safety policy remain strings to avoid unbounded keyword interning - Limited Java interop is also allowed in `:function` code (small allowlist): `java.time.*`, `java.time.format.DateTimeFormatter`, `java.time.temporal.{ChronoUnit,TemporalAdjusters}`, `java.util.{UUID,Base64}`, `java.math.{BigInteger,BigDecimal}`. Prefer `breyta.sandbox`. - Use the documented `json/*` helpers for JSON parsing and writing; lower-level parser namespaces are not part of the supported sandbox surface. ## Canonical Example ```clojure ;; In the flow definition: ;; :functions [{:id :normalize ;; :language :clojure ;; :code "(fn [input]\n;; {:value (str (:value input))})"}] (flow/step :function :normalize {:ref :normalize :input {:value 42}}) ``` No-input helper: ```clojure (flow/step :function :build-static-report-id {:code '(fn [_] "daily-ops-report")}) ``` JSON parsing example: ```clojure (flow/step :function :parse-payload {:code "(fn [input] (let [payload (json/parse (:body input))] {:event (:event payload) :id (get-in payload [:data :id])}))" :input {:body body-text}}) ``` Persist transformed rows as a table resource: ```clojure (flow/step :function :persist-enriched-rows {:code '(fn [input] (:rows input)) :input {:rows enriched-rows} :persist {:type :table :table "enriched_todos" :write-mode :append}}) ``` ## Related - [Start Here](/docs/start-here) - [Flow Authoring](/docs/build-flow-authoring) - [Flow Definition](/docs/reference-flow-definition) - [Functions](/docs/reference-functions) - [CLI Commands](/docs/reference-cli-commands) --- Document: Step Notify URL: https://flows.breyta.ai/docs/reference-step-notify.md HTML: https://flows.breyta.ai/docs/reference-step-notify Last updated: 2026-02-13T15:21:05+01:00 # Step Notify (`:notify`) ## Quick Answer Use `:notify` to send outbound notifications through HTTP-based channels (email APIs, chat webhooks, custom endpoints). `{:type :notify :channels {:http {...}}}` ## Canonical Shape | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:type` | keyword | Yes | Must be `:notify` | | `:channels` | map | Yes | Channel configs (currently `:http`) | | `:data` | map | No | Template data merged into channel payloads | | `:template` | keyword/string | No | Flow template id (channel config base) | | `:review` | bool/string/map | No | Human review checkpoint before send | | `:retry` | map | No | Common step retry policy | | `:timeout` | int | No | Common step timeout (seconds) | | `:persist` | map | No | Common output persistence options | ## Limits And Behavior - `:notify` step accepts external delivery channels only. - `:channels :ui` is rejected for `:notify`. - Use `:wait` + `:notify` for in-app approval flows. ## HTTP Channel Config Inside `:channels {:http ...}`, use the same request model as HTTP step channels: | Field | Required | Notes | | --- | --- | --- | | `:connection` or `:url` | Yes | Prefer connection reuse | | `:path` | No | Appended to connection base URL | | `:method` | No | Defaults from your channel contract | | `:headers`, `:query`, `:json`, `:form`, `:body` | No | Standard payload config | | `:template` / `:data` | No | Handlebars templating inputs | ## Result Shape The step returns per-channel delivery status: ```edn {:sent? true :channels {:http {:sent? true :status 202}}} ``` If one or more channels fail, `:sent?` is `false` and an `:errors` vector is included. ## Canonical Example ```clojure {:templates [{:id :ops-alert :type :notification :channels {:http {:connection :notify-api :path "/alerts" :method :post :json {:title "{{title}}" :severity "{{severity}}" :message "{{message}}"}}}] :functions [{:id :alert-payload :language :clojure :code "(fn [input]\n {:title \"Workflow alert\"\n :severity (or (:severity input) \"warning\")\n :message (str \"Run \" (:run-id input) \" requires attention\")})"}] :flow '(let [payload (flow/step :function :build-alert-payload {:ref :alert-payload :input (flow/input)})] (flow/step :notify :send-alert {:template :ops-alert :data payload}))} ``` ## Related - [Step HTTP](/docs/reference-step-http) - [Step Wait](/docs/reference-step-wait) - [Notifications And Email](/docs/guide-notifications-and-email) - [Templates](/docs/reference-templates) - [Limits And Recovery](/docs/reference-limits-and-recovery) --- Document: Step Job URL: https://flows.breyta.ai/docs/reference-step-job.md HTML: https://flows.breyta.ai/docs/reference-step-job Last updated: 2026-04-17T22:47:47+02:00 # Step Job (`:job`) ## Quick Answer Use `flow/step :job` to submit external work into Breyta's jobs control plane, read durable job state, and await terminal job or batch results from workers running through `breyta jobs worker run`. Use this when the flow should orchestrate external execution but keep the workflow boundary explicit in the step graph. ## Canonical Shape Core fields: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:type` | keyword | Yes | Must be `:job` | | `:op` | keyword/string | Yes | One of `:submit`, `:submit-batch`, `:get`, `:get-batch`, `:await`, `:await-batch` | Per-op fields: | Op | Required fields | Optional fields | Notes | | --- | --- | --- | --- | | `:submit` | `:job-type` | `:payload`, `:metadata`, `:max-attempts` | Create one queued job | | `:submit-batch` | `:job-type`, `:jobs` | `:metadata`, `:max-attempts` | Create one queued batch | | `:get` | `:job-id` | none | Read the latest state of one job | | `:get-batch` | `:batch-id` | `:include-jobs?`, `:limit` | Read the latest state of one batch | | `:await` | `:job-id` | `:interval`, `:timeout`, `:max-attempts`, `:backoff` | Wait for one job to reach a terminal state | | `:await-batch` | `:batch-id` | `:interval`, `:timeout`, `:max-attempts`, `:backoff`, `:include-jobs?`, `:limit` | Wait for one batch to reach a terminal state | ## Canonical Examples Submit and await one job: ```clojure (let [queued-job (flow/step :job :submit-review {:op :submit :job-type "agent-review" :payload {:surface "flows-api"}}) final-job (flow/step :job :await-review {:op :await :job-id (:job-id queued-job) :timeout "5m"})] final-job) ``` Submit and await one batch: ```clojure (let [queued-batch (flow/step :job :submit-review-batch {:op :submit-batch :job-type "codex-security-review" :jobs [{:payload {:surface "flows-api"}} {:payload {:surface "runtime"}}]}) final-batch (flow/step :job :await-review-batch {:op :await-batch :batch-id (:batch-id queued-batch) :timeout "45m" :include-jobs? true})] final-batch) ``` ## Terminal Statuses Job terminal statuses: - `"succeeded"` - `"no_changes"` - `"failed"` - `"cancelled"` - `"timed_out"` Batch terminal statuses: - `"completed"` - `"failed"` - `"partially_failed"` - `"cancelled"` ## Worker Contract The `:job` step orchestrates the control plane only. Worker setup and result construction live on the CLI worker side. For the worker contract, artifacts, and resource attachments, see [Jobs Control Plane](/docs/reference-flow-jobs). ## Related - [Jobs Control Plane](/docs/reference-flow-jobs) - [Step Reference](/docs/reference-step-reference) - [CLI Commands](/docs/reference-cli-commands) --- Document: Step KV URL: https://flows.breyta.ai/docs/reference-step-kv.md HTML: https://flows.breyta.ai/docs/reference-step-kv Last updated: 2026-02-13T15:21:05+01:00 # Step KV (`:kv`) ## Quick Answer Use `:kv` for workspace-scoped key/value state that must survive across steps and runs. `{:type :kv :operation :get|:set|:delete|:exists|:increment|:append|:list-keys ...}` ## Canonical Shape | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:type` | keyword | Yes | Must be `:kv` | | `:operation` | keyword/string | Yes | One of `:get`, `:set`, `:delete`, `:exists`, `:increment`, `:append`, `:list-keys` | | `:key` | string | Depends | Required for all key-based operations | | `:default` | any | No | `:get` fallback when key missing/expired | | `:value` | any | No | `:set` payload | | `:ttl` | int | No | TTL in seconds (`1` to `31536000`) | | `:metadata` | map | No | Optional metadata with `:set` | | `:if-not-exists` | bool | No | Conditional create on `:set` | | `:delta` | int/double | No | Increment amount (`:increment`) | | `:item`, `:items` | any/vector | No | Items to append (`:append`) | | `:max-length` | int | No | Max list size for `:append` (default `1000`) | | `:prefix` | string | No | Prefix filter for `:list-keys` | | `:limit` | int | No | Max keys for `:list-keys` | ## Limits And Behavior - Key length: max `256` chars. - Key charset: `a-z`, `A-Z`, `0-9`, `_`, `-`, `:`. - Value size: max `1 MB` serialized. - `:ttl` max: `31536000` seconds (1 year). - `:list-keys` limit max: `1000`. - Expired keys are treated as missing during reads/exists/list. ## Result Shapes Representative results: ```edn ;; :get {:success true :value {:foo "bar"} :exists? true} ;; :set {:success true :key "session:123" :created? true} ;; :increment {:success true :key "quota:user-42" :value 11 :previous 10} ;; :list-keys {:success true :keys ["session:1" "session:2"] :count 2} ``` ## Canonical Example ```clojure {:functions [{:id :session-key :language :clojure :code "(fn [input]\n {:key (str \"session:\" (:session-id input) \":history\")})"}] :flow '(let [k (flow/step :function :build-session-key {:ref :session-key :input (flow/input)}) history (flow/step :kv :load-history {:operation :get :key (:key k) :default []}) updated (conj history {:at (flow/now-ms) :message \"hello\"})] (flow/step :kv :save-history {:operation :set :key (:key k) :value updated :ttl 86400}))} ``` ## Related - [Step Function](/docs/reference-step-function) - [Persisted Results And Resources](/docs/guide-persisted-results-and-resources) - [Limits And Recovery](/docs/reference-limits-and-recovery) --- Document: Step Files URL: https://flows.breyta.ai/docs/reference-step-files.md HTML: https://flows.breyta.ai/docs/reference-step-files Last updated: 2026-05-15T14:59:18+02:00 # Step Files (`:files`) ## Quick Answer Use `:files` to: - resolve an immutable `source-tree-ref` from a source spec such as `:git` - fork a persistent `changeset-ref` from that source tree - query either resource through `:list`, `:read`, and `:search` - mutate only the `changeset-ref` through `:write-file`, `:apply-edit`, `:replace`, `:replace-lines`, `:delete-file`, and `:move-file` - render the current logical view into a temporary local directory through `:materialize` - capture filesystem edits from that directory back into the changeset through `:capture` - compare the current logical view to the base source through `:diff` - publish a `changeset-ref` to a branch through `:publish` - open or reuse a pull request from that published branch through `:open-change-request` Canonical import shape: `{:type :files :op :resolve-source :source {:type :git :repo "..." :ref "main"}}` Canonical writable flow: 1. `:resolve-source` -> `source-tree-ref` 2. `:init-changeset` -> `changeset-ref` 3. mutate the changeset 4. query the overlaid current view through the same read/list/search ops 5. `:publish` the changeset to a branch 6. `:open-change-request` from that published branch Physical model: - the runtime may clone/fetch into a temporary worker directory during import - that temporary checkout is deleted after import - the canonical source tree and changeset manifests live in Breyta's managed storage - later `:list`, `:read`, `:search`, `:materialize`, and `:diff` operate on the persisted logical view; the runtime may use a disposable temporary local source cache for unchanged files, but that cache is not authoritative ## Canonical Shape Core fields: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:type` | keyword | Yes | Must be `:files` | | `:op` | keyword/string | Yes | File operation. See operation sections below. | ### `:resolve-source` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:source.type` | keyword | Yes | Currently supports `:git` only | | `:source.repo` | string | Yes | Repo URL or fetchable git remote | | `:source.ref` | string | No | Ref to fetch (default `HEAD`) | | `:source.provider` | keyword/string | No | Explicit source provider, for example `:github` or a registered custom provider | | `:source.connection` | keyword/string | No | Normal Breyta connection/binding reference for source access | | `:source.sparse-paths` | vector | No | Restrict imported paths to selected prefixes | Connection note: - git imports resolve `:source.connection` through the normal connection model - for GitHub `https://github.com/...` sources, the resolved connection must currently be an `:http-api` connection so the runtime can use the GitHub REST/archive import path - local repositories and non-GitHub git remotes use direct git import when no connection is supplied - connection-backed non-GitHub imports require an explicit registered `:source.provider` ### `:init-changeset` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:source` | resource ref or URI | Yes | A `source-tree-ref` or an existing `changeset-ref` | This creates a new persisted `changeset-ref`. When the input is already a changeset, the new one forks that overlay state instead of mutating the parent. ### `:list` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:source` | resource ref or URI | Yes | `source-tree-ref` or `changeset-ref` | | `:path-prefix` | string | No | Filter listed paths by prefix | | `:limit` | int | No | `1` to `2000` (default `200`) | ### `:read` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:source` | resource ref or URI | Yes | `source-tree-ref` or `changeset-ref` | | `:path` | string | Conditionally | Read one path | | `:paths` | vector | Conditionally | Read up to `50` paths | | `:line-start` | int | No | Optional 1-based inclusive start line for bounded single-file reads | | `:line-end` | int | No | Optional 1-based inclusive end line for bounded single-file reads | Exactly one of `:path` or `:paths` must be provided. When `:line-start` or `:line-end` is present: - use `:path`, not `:paths` - the slice is bounded to at most `400` lines - omitted `:line-end` reads from `:line-start` up to the bounded slice limit - bounded read results always include `:full-read-args` - bounded reads include `:recommended-edit-op`: `:replace-lines` for small edits, `:write-file` for full rewrites - `:replace-lines-args` appears only when the slice is safe for bounded block edit Full-file reads include copy-forward `:write-file-args`. ### `:search` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:source` | resource ref or URI | Yes | `source-tree-ref` or `changeset-ref` | | `:query` | string | Yes | Case-insensitive substring search | | `:path-prefix` | string | No | Restrict the search to a subtree | | `:limit` | int | No | `1` to `100` (default `20`) | Content hits include `:read-args`, which you can usually copy directly into a bounded follow-up `:read`. ### `:write-file` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:changeset` | resource ref or URI | Yes | Target `changeset-ref` | | `:path` | string | Yes | Path to create or overwrite in the logical view | | `:content` | string | Yes | Full file content | | `:content-type` | string | No | Defaults to the existing file type or `text/plain` | ### `:apply-edit` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:changeset` | resource ref or URI | Yes | Target `changeset-ref` | | `:path` | string | Yes | Existing text file path in the current logical view | | `:edit.op` | keyword/string | Yes | Supports `:replace` and `:replace-lines` | | `:edit.match` | string | Conditionally | Exact substring to replace when `:edit.op` is `:replace`; must be non-empty | | `:edit.all?` | boolean | No | Replace all exact non-overlapping matches for `:replace`; default is exactly one match | | `:edit.line-start` | int | Conditionally | 1-based inclusive start line when `:edit.op` is `:replace-lines` | | `:edit.line-end` | int | Conditionally | 1-based inclusive end line when `:edit.op` is `:replace-lines` | | `:edit.expected` | string | Conditionally | Exact current content of the selected line window when `:edit.op` is `:replace-lines` | | `:edit.replace` | string | Yes | Replacement text | `:`**`apply-edit` remains supported, but direct `:replace` and `:replace-lines` ops are preferred for agent/tool use.** ### `:replace` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:changeset` | resource ref or URI | Yes | Target `changeset-ref` | | `:path` | string | Yes | Existing text file path in the current logical view | | `:match` | string | Yes | Exact substring to replace; must be non-empty | | `:replace` | string | Yes | Replacement text | | `:all?` | boolean | No | Replace all exact non-overlapping matches; default is exactly one match | ### `:replace-lines` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:changeset` | resource ref or URI | Yes | Target `changeset-ref` | | `:path` | string | Yes | Existing text file path in the current logical view | | `:line-start` | int | Yes | 1-based inclusive start line | | `:line-end` | int | Yes | 1-based inclusive end line | | `:expected` | string | Yes | Exact current content of the selected line window | | `:replace` | string | Yes | Replacement text for that line window | ### `:delete-file` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:changeset` | resource ref or URI | Yes | Target `changeset-ref` | | `:path` | string | Yes | Path to remove from the logical view | ### `:move-file` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:changeset` | resource ref or URI | Yes | Target `changeset-ref` | | `:from-path` | string | Yes | Existing path in the logical view | | `:to-path` | string | Yes | Destination path in the logical view | ### `:diff` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:source` | resource ref or URI | Yes | Usually a `changeset-ref`; a plain source tree returns an empty diff | ### `:materialize` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:source` | resource ref or URI | Yes | `source-tree-ref` or `changeset-ref` | This renders the current logical view into a materialized working area. When the files service is enabled, the working area is service-owned and the step returns a `files-session://...` handle plus lease fields instead of a raw directory path. Without the files service, the fallback implementation returns a temporary worker-local directory. It is a one-way execution bridge only: - persisted source-tree and changeset resources remain authoritative - filesystem-side mutations do not write back into the logical resource state - returned directories are temporary and disposable, not durable resource refs - service-owned sessions are durable handoff handles, but callers must use the returned `:lease-id` and `:generation` for follow-up `:capture` calls - deployed workers should point the materialization root and source-cache root at an explicit size-limited disk-backed mount such as `/var/lib/flows-worker/files/...`, instead of relying on generic process temp space inside the pod To import changes made on disk back into the changeset, use `:capture`. ### `:capture` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:changeset` | resource ref or URI | Yes for worker-local directory capture; optional for service-owned session capture when the materialized session already has a changeset | Target `changeset-ref` to write captured files into | | `:directory` | string | Yes for worker-local fallback capture | Path to the local directory to capture (typically from fallback `:materialize`) | | `:session-uri` | string | Yes for files-service capture | `files-session://...` handle returned by service-backed `:materialize` | | `:lease-id` | string | Yes for files-service capture | Current lease id returned by `:materialize` or the latest heartbeat/capture | | `:generation` | integer | Yes for files-service capture | Current generation returned by `:materialize` or the latest heartbeat/capture | | `:path-prefix` | string | No | Prefix added to captured file paths | This is the reverse of `:materialize` — it reads files from a materialized working area and writes any changes back into the changeset. Only files that differ from the current changeset view are written; unchanged files are skipped. Capture treats the directory as the desired state for the selected scope: - changed files are written into the changeset - new empty files are captured like any other file - files missing from the directory are captured as deletes - files larger than the capture size limit fail explicitly instead of being silently skipped Typical pattern: ```clojure '(let [mat (flow/step :files :materialize {:op :materialize :source changeset}) ;; Run an external tool against the materialized directory _ (flow/step :job :run-linter {:directory (:directory mat)}) ;; Capture any fixes the tool made back into the changeset updated (flow/step :files :capture-fixes {:op :capture :changeset changeset :directory (:directory mat)})] updated) ``` With files-service sessions, capture uses the returned handle and lease instead of a directory: ```clojure '(let [mat (flow/step :files :materialize {:op :materialize :source changeset}) updated (flow/step :files :capture-fixes {:op :capture :session-uri (:session-uri mat) :lease-id (:lease-id mat) :generation (:generation mat)})] updated) ``` If the session was materialized from a `source-tree-ref` rather than a `changeset-ref`, include `:changeset` in the capture request so the service has an explicit writeback target. ### `:heartbeat`, `:status`, `:release` These lifecycle ops only apply to files-service sessions. | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:session-uri` | string | Yes | `files-session://...` handle returned by service-backed `:materialize` | | `:lease-id` | string | Yes for `:heartbeat` and `:release` | Current lease id | | `:generation` | integer | Yes for `:heartbeat` and `:release` | Current generation | - `:heartbeat` renews the lease expiration without capturing changes. - `:status` returns the public session metadata. - `:release` marks the session unusable and lets the files service reclaim the service-owned working directory. - Expired sessions reject further heartbeat/capture/release attempts. The files service also reclaims expired service-owned roots on startup and before new materialization allocation so abandoned sessions do not keep consuming workspace disk quota indefinitely. ### `:publish` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:changeset` | resource ref or URI | Yes | Target `changeset-ref` to publish | | `:provider` | keyword/string | No | Provider to publish through; defaults from the source origin, or is inferred from the repository when possible | | `:connection` | keyword/string | No | Normal Breyta `:http-api` connection/binding for the repository provider; defaults from the source origin when available | | `:repository` | string | No | `owner/repo`; defaults from the imported GitHub repo when possible | | `:base-branch` | string | No | Defaults to the imported source ref when possible, otherwise `main` | | `:branch` | string | Yes | Target branch name for the published commit | | `:commit-message` | string | Yes | Commit message for the published change | | `:allow-target-override?` | boolean | No | Explicitly allow publishing to a different repo, base branch, or connection than the imported source-tree origin | | `:force?` | boolean | No | When true, update an existing branch ref instead of failing on collision | Current behavior: - GitHub is the built-in provider; registered custom providers can handle their own publish path - branch/commit publication happens through the provider API, not a temporary local git worktree - the bound GitHub connection must be able to read repository metadata/branches and create blobs, trees, commits, refs, and pull requests for the target repository - local validation or tests are not run on the flows worker before publishing - by default `:publish` refuses provider/repo/branch/connection retargeting away from the changeset origin - set `:allow-target-override? true` only for intentional retargeting - `:allow-target-override?` is an authored runtime escape hatch, not an agent-facing affordance. Built-in `:files` tool calls do not let the model set it directly. ### `:open-change-request` | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:provider` | keyword/string | No | Provider to use; defaults from `:published` when supplied, or is inferred from the repository when possible | | `:connection` | keyword/string | No | Normal Breyta `:http-api` connection/binding for the repository provider; defaults from `:published` when supplied | | `:published` | map | Conditionally | Publish result map from `:files :publish` | | `:repository` | string | Conditionally | `owner/repo` when not using `:published` | | `:base-branch` | string | Conditionally | Base branch when not using `:published` | | `:branch` | string | Conditionally | Published head branch when not using `:published` | | `:change-title` | string | Yes | Pull request title | | `:change-body` | string | Yes | Pull request body | | `:allow-target-override?` | boolean | No | Explicitly allow repo, base-branch, or connection divergence from the supplied `:published` result | | `:draft?` | boolean | No | Create the pull request as draft when supported | Provide either: - `:published` from `:files :publish`, or - explicit `:repository`, `:base-branch`, and `:branch` When `:published` is supplied, `:open-change-request` preserves that published target by default and rejects conflicting repo, base-branch, or connection overrides unless `:allow-target-override? true` is set. Like `:publish`, `:allow-target-override?` on `:open-change-request` is intended for authored flow logic only. If an agent needs an intentional retargeting surface, package that behavior behind a flow-local packaged `:steps` definition. ## Runtime Behavior - `:resolve-source` currently imports git repositories only. - `:resolve-source` uses the bound connection and disables ambient git credential helpers. - GitHub sources with a bound `:http-api` connection use GitHub REST/archive reads. - GitHub `401`/`403` can retry anonymously for public repositories. - Anonymous retry does not bypass private repo auth. - GitHub credential failures keep `connection-id` in error context. - local repositories and non-GitHub git sources still use the direct git import path. - `:publish` and `:open-change-request` resolve auth from the same bound `:http-api` connection rather than ambient worker credentials, so branch writes and pull-request creation should reflect the configured connection's repository access. - `:resolve-source` returns an immutable `source-tree-ref`. - repeated `:resolve-source` calls for the same repo, resolved commit, sparse-path selection, and retention window may reuse the existing stored `source-tree-ref` instead of reimporting file content. - `:init-changeset` returns a persisted logical overlay over that source tree. - `:write-file`, `:apply-edit`, `:replace`, `:replace-lines`, `:delete-file`, and `:move-file` only mutate the changeset resource. - `:list`, `:read`, and `:search` work against the current logical view: - a plain source tree if `:source` is a `source-tree-ref` - the source tree plus changeset overlay if `:source` is a `changeset-ref` - text files are stored as persisted content in Breyta storage. - repeated `:read` and `:search` calls may use a local cache for unchanged source-tree files instead of re-fetching from storage. - this cache is only an optimization; the stored source-tree and changeset resources are authoritative. - binary files still appear in `:list`, can be moved/deleted through a changeset, and can match path searches. - imported source trees persist binary file bytes as well as text file bytes so `:materialize` can render the full current logical view. - `:read` rejects binary files instead of returning inline bytes. - `:read` supports bounded single-file slices through `:line-start` / `:line-end`. - bounded `:read` results include `:line-start`, `:line-end`, `:total-lines`, `:truncated?`, `:recommended-edit-op`, and `:full-read-args`. - bounded `:read` results only include copy-forward `:replace-lines-args` when the reread slice is small enough for a safe bounded block edit. - full-file single-path `:read` results include copy-forward `:write-file-args` and `:recommended-edit-op` set to `:write-file`. - `:search` skips binary content reads but still matches binary paths by filename/path text. - content search hits include `:match-line` and copy-forward `:read-args` for the matched file. - `:apply-edit` only works on existing text files in the logical view. - `:apply-edit` supports two deterministic edit modes: - `:replace` for exact substring replacement - `:replace-lines` for bounded line-window replacement with stale-slice protection - direct `:replace` and `:replace-lines` ops are aliases for those same deterministic edit modes and are the preferred surface for agent/tool calls. - `:apply-edit :replace` is best for short exact snippets you just reread, and fails when `:edit.match` is missing. - `:apply-edit :replace` also fails on multiple matches unless `:edit.all? true` is set. - when a multiline exact replace cannot be matched safely, the validation message explicitly guides callers toward a bounded reread plus `:replace-lines`. - `:apply-edit :replace-lines` requires `:edit.line-start`, `:edit.line-end`, `:edit.expected`, and `:edit.replace`. - `:apply-edit :replace-lines` is bounded to at most `400` lines and fails if the current line window no longer matches `:edit.expected`; reread the file slice and retry in that case. - prefer `:write-file` after a full-file read when you are replacing most of a file or a whole function/body that no longer fits comfortably inside a bounded reread window. - without the files service, `:materialize` writes the current logical view to a temporary local directory under the configured materialization root (or the process temp directory by default). - deployed environments should set explicit size-limited disk-backed roots for both materialization and source-cache paths; in `breyta-env` the intended worker mount is `/var/lib/flows-worker/files`. - `:materialize` is one-way by itself: if a later tool edits files in that directory, use `:capture` to sync those edits back into the `changeset-ref`. - `:publish` targets the selected source provider and publishes the current logical diff through that provider. The built-in GitHub provider creates provider-side blobs, a tree, a commit, and a branch ref. - the built-in GitHub provider resolves the base branch through GitHub repository branch/commit APIs before creating new Git database objects, which gives clearer errors for missing branches or inaccessible repositories. - `:publish` uses regular file mode (`100644`); executable mode metadata is not preserved. - `:open-change-request` targets the selected source provider. The built-in GitHub provider will reuse an existing open pull request for the published branch when GitHub rejects duplicate creation. - success for this fixer path is bounded edit + publish + pull request; local validation on the flows worker is not part of the contract. - source trees and changesets inherit bounded TTL/retention in the persisted manifest/content/resource path. - structured resources such as tables remain table-native and are not flattened into `:files` automatically. ## Result Shapes ### `:resolve-source` Returns a `source-tree-ref` previewing the imported tree: ```clojure {:type :resource-ref :uri "res://..." :content-type "application/vnd.breyta.source-tree+json" :preview {:source-type :git :repo "https://github.com/acme/repo.git" :ref "main" :commit-sha "abc123..." :file-count 42}} ``` ### `:init-changeset`, `:write-file`, `:apply-edit`, `:delete-file`, `:move-file` These return the current `changeset-ref`: ```clojure {:type :resource-ref :uri "res://..." :content-type "application/vnd.breyta.files-changeset+json" :preview {:source-uri "res://...source-tree..." :change-count 3}} ``` ### `:list` ```clojure {:source "res://..." :count 3 :limit 200 :items [{:path "README.md" :size-bytes 121 :content-type "text/markdown" :binary? false} {:path "assets/logo.bin" :size-bytes 2048 :content-type "application/octet-stream" :binary? true}]} ``` ### `:read` ```clojure {:source "res://..." :count 1 :items [{:path "src/app/core.clj" :size-bytes 84 :content-type "text/plain" :content "(def persisted-value \"ready\")" :line-start 2 :line-end 2 :total-lines 2 :truncated? true}]} ``` ### `:search` ```clojure {:source "res://..." :query "persisted-value" :count 1 :limit 20 :results [{:path "src/app/core.clj" :match :content :snippet "...persisted-value..." :size-bytes 84 :content-type "text/plain"}]} ``` ### `:diff` ```clojure {:source "res://...changeset..." :base-source "res://...source-tree..." :count 2 :changes [{:path "README.md" :change :modified :before-size-bytes 14 :size-bytes 22 :content-type "text/markdown" :binary? false} {:path "docs/notes.txt" :change :deleted :size-bytes 32 :content-type "text/plain" :binary? false}]} ``` ### `:materialize` ```clojure {:source "res://...changeset..." :base-source "res://...source-tree..." :directory "/mounted/worker-tmp/breyta-files-materializations/ws-acme-abc123" :count 4 :text-file-count 3 :binary-file-count 1 :size-bytes 1234 :lifecycle {:storage :worker-local :authoritative? false :writeback? false}} ``` Service-backed materialization returns a stable session handle without exposing a raw directory: ```clojure {:session-uri "files-session://ws-acme/..." :lease-id "..." :generation 1 :expires-at "2026-04-26T08:00:00Z" :root-kind :service-owned :materialized? true :lifecycle {:storage :files-service :durable-handoff? true :raw-directory-exposed? false}} ``` ### `:capture` ```clojure {:changeset {:type :resource-ref :uri "res://..." :content-type "application/vnd.breyta.files-changeset+json" :preview {:source-uri "res://...source-tree..." :change-count 3}} :captured-files 2 :skipped-files 41 :directory "/mounted/worker-tmp/breyta-files-materializations/ws-acme-abc123"} ``` Service-backed capture returns the renewed lease generation: ```clojure {:session-uri "files-session://ws-acme/..." :lease-id "..." :generation 2 :changeset {:type :resource-ref :uri "res://...changeset..."} :capture-summary {:captured-files 2 :skipped-files 41}} ``` ### `:publish` ```clojure {:status "published" :provider :github :source "res://...source-tree..." :changeset "res://...changeset..." :repository "acme/repo" :base-branch "main" :branch "codex/security-fix/test" :base-commit-sha "abc123..." :tree-sha "def456..." :commit-sha "fedcba..." :ref-action :created :change-count 2 :changes [{:path "docs/notes.txt" :change :deleted ...} {:path "src/app/core.clj" :change :modified ...}]} ``` When the changeset has no logical diff, `:publish` returns: ```clojure {:status "no_changes" :provider :github :repository "acme/repo" :base-branch "main" :branch "codex/security-fix/test" :change-count 0 :changes []} ``` ### `:open-change-request` ```clojure {:status "created" :provider :github :repository "acme/repo" :base-branch "main" :branch "codex/security-fix/test" :number 42 :url "https://github.com/acme/repo/pull/42"} ``` If the provider reports that the pull request already exists, the step reuses the existing open pull request and returns `:status "existing"`. ## Canonical Examples Resolve a repo, fork a changeset, mutate it, and query the overlaid view: ```clojure '(let [repo-tree (flow/step :files :resolve-repo {:op :resolve-source :source {:type :git :repo "https://github.com/acme/service.git" :ref "main" :connection :github-api}}) draft (flow/step :files :start-draft {:op :init-changeset :source repo-tree}) draft (flow/step :files :update-readme {:op :write-file :changeset draft :path "README.md" :content "# Updated service\n"}) draft (flow/step :files :edit-core {:op :replace :changeset draft :path "src/app/core.clj" :match "\"ready\"" :replace "\"steady\""}) draft (flow/step :files :tighten-guard {:op :replace-lines :changeset draft :path "src/app/core.clj" :line-start 10 :line-end 14 :expected "(when insecure?\n (log/warn \"legacy path\")\n true)" :replace "(when insecure?\n (log/error \"legacy path\")\n false)"}) draft (flow/step :files :move-core {:op :move-file :changeset draft :from-path "src/app/core.clj" :to-path "src/app/main.clj"}) draft (flow/step :files :delete-notes {:op :delete-file :changeset draft :path "docs/notes.txt"}) files (flow/step :files :list-draft {:op :list :source draft}) matches (flow/step :files :search-draft {:op :search :source draft :query "persisted-value"}) diff (flow/step :files :diff-draft {:op :diff :source draft})] {:files files :matches matches :diff diff :draft draft}) ``` Publish a bounded draft and open or reuse a pull request: ```clojure '(let [repo-tree (flow/step :files :resolve-repo {:op :resolve-source :source {:type :git :repo "https://github.com/acme/service.git" :ref "main" :connection :github-api}}) draft (flow/step :files :start-draft {:op :init-changeset :source repo-tree}) draft (flow/step :files :edit-core {:op :apply-edit :changeset draft :path "src/app/core.clj" :edit {:op :replace :match "\"ready\"" :replace "\"steady\""}}) published (flow/step :files :publish-draft {:op :publish :changeset draft :connection :github-api :repository "acme/service" :base-branch "main" :branch "codex/security-fix/test" :commit-message "security: fix runtime issue"}) pr (flow/step :files :open-pr {:op :open-change-request :connection :github-api :published published :change-title "Fix runtime issue" :change-body "Automated remediation from the security fixer flow."})] {:published published :pull-request pr}) ``` ## Source Providers `:files` routes provider-specific operations through a source provider registry. Today GitHub is the built-in provider, so only GitHub is auto-detected from the repository URL. Other forges must be set explicitly via `:provider` after their provider has been registered: ```clojure ;; Auto-detected as GitHub from the URL {:op :resolve-source :source {:type :git :repo "https://github.com/acme/service.git"}} ;; Explicit provider after registering a GitLab provider {:op :resolve-source :source {:type :git :repo "https://gitlab.com/acme/repo.git" :provider :gitlab}} ``` GitHub is the built-in provider. Custom providers can be registered for GitLab, Bitbucket, Azure DevOps, or any other forge. Provider-specific operations: `:resolve-source`, `:publish`, `:open-change-request`. Provider-agnostic operations (work with any source): `:list`, `:read`, `:search`, `:write-file`, `:apply-edit`, `:replace`, `:replace-lines`, `:delete-file`, `:move-file`, `:diff`, `:init-changeset`, `:materialize`, `:capture`. ## Connection And Installation Notes `:files` uses connections for two purposes: 1. **Source import** (`:resolve-source`) — reads repository content 2. **Publish/PR** (`:publish`, `:open-change-request`) — writes branches and pull requests For installable flows, declare the connection as a requirement: ```clojure ;; Installer provides their own GitHub token: {:requires [{:slot :github-api :type :http-api :label "GitHub API" :auth {:type :api-key} :base-url "https://api.github.com"}]} ;; Author provides the token (installer never configures GitHub): {:requires [{:slot :github-api :type :http-api :provided-by :author :base-url "https://api.github.com"}]} ``` Then reference the slot in the step: ```clojure (flow/step :files :resolve-repo {:op :resolve-source :source {:type :git :repo "https://github.com/acme/service.git" :ref "main" :connection :github-api}}) ``` The same connection is used for `:publish` and `:open-change-request`. Source-tree resources (`source-tree-ref`, `changeset-ref`) are persisted in Breyta's managed storage with bounded TTL. In installed flows, these are internal to the flow's execution — the installer does not see or manage them. ## Related - [Step Agent](/docs/reference-step-agent) — exposes an agent-safe subset of `:files` as a built-in tool - [Packaged Steps](/docs/reference-packaged-steps) - [Step Reference](/docs/reference-step-reference) - [Installations](/docs/guide-installations) — installable agent flows - [Flow Definition](/docs/reference-flow-definition) - [Persisted Results And Resource Refs](/docs/guide-persisted-results-and-resources) --- Document: Step Table URL: https://flows.breyta.ai/docs/reference-step-table.md HTML: https://flows.breyta.ai/docs/reference-step-table Last updated: 2026-05-15T14:59:18+02:00 # Step Table (`:table`) ## Quick Answer Use `flow/step :table` to query, inspect, export, edit, and evolve an existing table resource without exposing raw SQL. Table creation happens through `:persist {:type :table ...}` on another step. The `:table` step is the bounded runtime surface for working with that persisted table later. Worker mental model: - a normal flow step returns row-shaped data - `:persist {:type :table ...}` tells the runtime to write those rows into a table family, creating it on first write - the persisted step result becomes the canonical table `{:type :resource-ref :uri ...}` handle - later `:table` steps consume that ref; include it in final flow output when you want the run/resource UI to expose it directly For partitioned table families, query-like ops should select partition scope explicitly: - use the family root for `:schema` - use `{:ref :partitions {:key "..."}}` or `{:ref :partitions {:keys ["..." "..."]}}` for `:query`, `:get-row`, `:aggregate`, `:export`, `:update-cell`, `:update-cell-format`, `:set-column`, and `:recompute` - do not rely on implicit all-partitions scans ## Canonical Shape Common fields: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:type` | keyword | Yes | Must be `:table` | | `:op` | keyword/string | Yes | One of `:query`, `:get-row`, `:aggregate`, `:schema`, `:export`, `:update-cell`, `:update-cell-format`, `:set-column`, `:recompute`, `:materialize-join` | | `:table` | map/ref | Usually | Single-table target. Use `{:ref }`; add `:partitions` when needed. Bare refs work for simple ops. `:materialize-join` uses `:left`, `:right`, and `:into`. | | `:expect` | keyword | No | Standard step output expectation | | `:provider-opts` | map | No | Escape hatch options for advanced runtime integration | Per-op fields: | Op | Required fields | Optional fields | Notes | | --- | --- | --- | --- | | `:query` | `:table`, `:page` | `:select`, `:where`, `:sort` | Paged by default; `:page.mode` is explicit | | `:get-row` | `:table` plus `:row-id` or `:key` | none | Fetch one row by stable id or key fields | | `:aggregate` | `:table` | `:where`, `:group-by`, `:metrics`, `:having`, `:order-by`, `:limit` | Single-table aggregates only | | `:schema` | `:table` | none | Returns columns, key/index fields, and stats | | `:export` | `:table` | `:format`, `:select`, `:where`, `:sort` | V1 export format is `:csv` | | `:update-cell` | `:table`, `:column`, `:value`, plus `:row-id` or `:key` | none | Updates one canonical cell value | | `:update-cell-format` | `:table`, `:column`, plus `:row-id` or `:key` | `:format` | Sparse formatting override; omit/clear format to remove override | | `:set-column` | `:table`, `:column` | `:definition` | Create/update one logical column definition, including semantic/computed/reference/enum metadata; existing rows are backfilled automatically | | `:recompute` | `:table` | `:where`, `:limit`, `:offset` | Recompute materialized computed/reference columns for existing rows | | `:materialize-join` | `:left`, `:right`, `:on`, `:into` | `:join-type`, `:project`, `:op-id` | Build or refresh a destination table from a bounded key-based join; the same contract is also exposed through `breyta resources table materialize-join` for end-to-end validation | ## Predicate, Sort, And Metric Shapes ```clojure ;; Predicates [:status := "open"] [:amount :>= 100] [:title :contains "invoice"] ;; Agent/tool JSON may use {"field":"status","op":"=","value":"open"}. ;; Sort [:updated-at :desc] ;; Agent/tool JSON may use {"field":"updated-at","direction":"desc"}. ;; Metrics {:op :count :as :count} {:op :sum :field :amount :as :total-amount} {:op :count :where [[:status := "open"]] :as :open-count} {:op :arg-max :field :order-id :order-field :amount :as :largest-order-id} {:op :collect-set :field :currency :limit 5 :as :currencies} {:op :percentile :field :amount :p 0.95 :as :p95-amount} {:op :median :field :amount :as :median-amount} ;; Group-by bucket spec {:field :created-at :bucket {:op :date-trunc :unit :month} :as :created-month} {:field :amount :bucket {:op :numeric-bin :size 10} :as :amount-bin} ``` Supported predicate ops: - `:=` - `:!=` - `:>` - `:>=` - `:<` - `:<=` - `:contains` Supported aggregate metrics: - `:count` - `:sum` - `:avg` - `:min` - `:max` - `:count-distinct` - `:arg-max` - `:arg-min` - `:collect-set` - `:percentile` - `:median` Aggregate notes: - `:order-by` can reference group keys and metric aliases - `:having` can reference group keys and metric aliases - aggregate responses include `:limit` and `:has-more` when truncation is possible - metric-local `:where` enables bounded conditional metrics such as count-if and sum-if - `:arg-max` / `:arg-min` return the metric `:field` value from the row with the highest/lowest `:order-field`, with deterministic row-id tie-breaking - `:collect-set` returns bounded distinct values in deterministic order - `:percentile` uses continuous interpolation over the numeric values in the metric field with `:p` in the range `0.0..1.0` - `:median` is the `0.5` percentile over the numeric metric field - bucketed `:group-by` supports `{:bucket {:op :date-trunc :unit :day|:week|:month}}` and `{:bucket {:op :numeric-bin :size }}` - numeric-bin group keys return the inclusive lower bound of the bucket as a number ## Limits And Behavior - Table resources (families) per workspace max: `500` - Live rows per concrete table max: `50_000` - Columns per table max: `200` - Promoted/index fields per table max: `16` - Partitions per family max: `128` - Partitions touched per write max: `16` - Selected partitions per query/aggregate/export max: `12` - Selected partitions per read/schema max: `24` - Partition key bytes max: `256` - Cell max: `64 KB` - Row payload max: `256 KB` - Table max: `256 MB` - Workspace table DB max: `2 GB` - Rows per write max: `1000` - Query page size max: `1000` - Query scan window max: `10000` rows via `page.offset + page.limit` - Aggregate group max: `200` - `collect-set` default item max per metric: `10` - `collect-set` absolute item max per metric: `25` - `percentile` requires numeric `:p` between `0.0` and `1.0` - Single-table only - No arbitrary joins - `:materialize-join` is the only bounded join-like exception, and it always materializes into a destination table - No cross-workspace reads - No arbitrary SQL Dedicated `:materialize-join` limits: - Inline `:left {:rows ...}` max: `1000` rows - Table-source window max per side: `10000` rows via `:limit` and `:offset` - Output row max: `10000` - Join key max: `4` - Projected right-field max: `64` The `:table` step is intentionally bounded. It is meant to feel like a table resource primitive, not a general database query engine. Partitioned table families are first-class. Use `:partitioning` on `:persist {:type :table ...}` when data naturally splits by region, tenant, source, or date bucket and most reads/writes stay in one small partition set. Design guidance when a dataset approaches bounded-table limits: - keep `50_000` live rows per concrete table or partition as a real boundary - keep the family root as the schema/metadata owner and select partition scope explicitly for query-like operations instead of expecting implicit all-partitions scans - use separate explicit tables when the data truly represents different datasets or lifecycles, not just as a workaround for missing partition support - if the workload mainly needs wide cross-partition scans, arbitrary joins, or general database behavior, prefer a dedicated `:db` step and an external database/query backend The most common failures here are: - write rejected because the table would exceed `50_000` rows - write rejected because the table family would exceed `128` partitions - write rejected because a new observed column would exceed `200` columns - cell rejected because it exceeds `64 KB` - query rejected because `limit > 1000` - query rejected because `page.offset + page.limit > 10000` - query/export rejected because the selected partition subset exceeds the bounded family limit ## Create-On-Write Companion Pattern Create the table by persisting rows on first write: ```clojure (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] :indexes [{:field :status} {:field :customer-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}}]}}) ``` That step returns a table resource ref: ```clojure {:type :resource-ref :uri "res://v1/ws/ws-123/result/table/tbl_..." :preview {:table-name "orders" :write-mode :upsert :rows-written 100} :write {:mode :upsert :rows-written 100}} ``` Creation notes: - there is no separate "create table resource" step or registration call for workers - the runtime creates or updates the table family as part of the persisted write - downstream steps should keep passing the returned resource ref, not the original raw rows, when they mean "the persisted table" ## Return A Table As Final Output When the user should see a real table artifact in the run output, return the persisted table step result from a `:breyta.viewer/kind :table` viewer. This is different from returning a map that happens to contain `:rows` or `:columns`. ```clojure '(let [run-id (str "run-" (flow/now-ms)) comparison-table (flow/step :function :build-comparison-table {:input {:rows comparison-rows :run-id run-id} :code '(fn [{:keys [rows run-id]}] {:rows (mapv (fn [row] {:run_id run-id :paragraph (:paragraph row) :original (:original row) :cleaned (:cleaned row) :changed (:changed row)}) rows)}) :persist {:type :table :table (str "transcript-comparison-" run-id) :rows-path [:rows] :write-mode :upsert :key-fields [:run_id :paragraph] :columns [{:column :paragraph :display-name "Paragraph"} {:column :original :display-name "Original"} {:column :cleaned :display-name "Cleaned"} {:column :changed :display-name "Changed"}]}})] {:breyta.viewer/kind :table :breyta.viewer/options {:title "Original vs cleaned"} :breyta.viewer/value comparison-table}) ``` The table viewer value should be the resource ref returned by the persisted step: ```clojure {:type :resource-ref :uri "res://v1/ws/ws-123/result/table/tbl_..." :content-type "application/vnd.breyta.table+json" :preview {:rows-written 44}} ``` If the output page or run sidepeek says that no tables are available, verify the final output is not just inline preview data. Use `breyta resources read ` to confirm that the table resource has rows. If the table is part of a larger written report, use the Markdown output pattern instead: return a `:markdown` viewer envelope and embed the persisted table with a fenced `breyta-resource` block. Markdown table embeds can select columns, filter/sort rows, render aggregate charts, and add a separate `:view :download` fence for CSV source export. See [Output Artifacts](/docs/guide-output-artifacts#markdown-resource-embeds). Query it later with `:table`: ```clojure (flow/step :table :open-orders {:op :query :table {:ref orders-ref} :select [:order-id :status :amount] :where [[:status := "open"]] :sort [[:order-id :asc]] :page {:mode :offset :limit 25 :offset 0}}) ``` Cursor-paged forward scan: ```clojure (flow/step :table :scan-orders {:op :query :table {:ref orders-ref} :select [:order-id :status] :sort [[:order-id :asc]] :page {:mode :cursor :limit 250}}) ``` Query rules: - `:page` is required for `:op :query` - `:table {:ref }` is canonical; bare refs work for simple ops. - `:page.mode` must be `:offset` or `:cursor` - `:page.mode :offset` accepts `:offset` and does not accept `:cursor` - `:page.mode :cursor` accepts `:cursor`, requires explicit `:sort`, and does not accept `:offset` - the first cursor page omits `:page.cursor` - cursor-paged `:page.total-count` is optional ## Canonical Examples Get one row: ```clojure (flow/step :table :load-order {:op :get-row :table {:ref orders-ref} :key {:order-id "ord-1"}}) ``` Aggregate: ```clojure (flow/step :table :sales-by-currency {:op :aggregate :table {:ref orders-ref} :group-by [:currency] :metrics [{:op :count :where [[:status := "open"]] :as :open-count} {:op :sum :field :amount :as :total-amount} {:op :arg-max :field :order-id :order-field :amount :as :largest-order-id}] :order-by [[:total-amount :desc]] :limit 20}) (flow/step :table :sales-by-month {:op :aggregate :table {:ref orders-ref} :group-by [{:field :created-at :bucket {:op :date-trunc :unit :month} :as :created-month}] :metrics [{:op :count :as :count} {:op :collect-set :field :currency :limit 5 :as :currencies}] :having [[:count :>= 2]] :order-by [[:created-month :asc]] :limit 12}) (flow/step :table :amount-distribution {:op :aggregate :table {:ref orders-ref} :group-by [{:field :amount :bucket {:op :numeric-bin :size 10} :as :amount-bin}] :metrics [{:op :count :as :count} {:op :percentile :field :amount :p 0.95 :as :p95-amount} {:op :median :field :amount :as :median-amount}] :order-by [[:amount-bin :asc]] :limit 12}) ``` Export: ```clojure (flow/step :table :export-orders {:op :export :table {:ref orders-ref} :format :csv :select [:order-id :status :amount]}) ``` By default, in-flow `:export` returns the CSV text inline. Add top-level `:persist {:type :blob ...}` when the export should become a downloadable resource ref: ```clojure (flow/step :table :export-orders-csv {:op :export :table {:ref orders-ref} :format :csv :select [:order-id :status :amount] :persist {:type :blob :filename "orders.csv" :content-type "text/csv"}}) ``` Materialize a joined destination table: ```clojure (flow/step :table :materialize-orders-with-customers {:op :materialize-join :left {:rows [{:order-id "ord-1" :customer-id "cust-1" :status "open"}]} :right {:table "customers" :select [:customer-id :name :domain]} :join-type :left :on [{:left-field :customer-id :right-field :customer-id}] :project {:keep-left :all :right-fields [{:field :name :as :customer-name} {:field :domain :as :customer-domain}]} :into {:table "orders-enriched" :write-mode :upsert :key-fields [:order-id] :index-fields [:customer-name :status]}}) ``` `materialize-join` is incremental materialization in v1: - `:into :write-mode` is `:append` or `:upsert` - there is no snapshot or `:replace` mode yet - existing destination rows are not deleted automatically when the source set shrinks The same applies to normal table persists with `:write-mode :upsert`: matching keys are updated, new keys are inserted, and omitted rows stay in the table. If a flow represents a current snapshot, include a run/batch key in the table keys or partitioning and query the latest batch. Do not expect a smaller rerun to delete rows from an earlier larger extraction. `materialize-join` also uses the current materialized row state of source tables: - computed/reference column values already materialized into the source table are joinable/projectable - the join does not re-evaluate computed expressions dynamically - run `:recompute` first if source-table derived values need to be refreshed before the join Update one value: ```clojure (flow/step :table :close-order {:op :update-cell :table {:ref orders-ref} :key {:order-id "ord-1"} :column :status :value "closed"}) ``` For partitioned table families, `:update-cell` cannot modify the partition-driving field. Single-cell updates stay within the explicitly selected partition; moving a row to a different partition requires a normal write/upsert into the target table. Update one formatting override: ```clojure (flow/step :table :format-amount {:op :update-cell-format :table {:ref orders-ref} :key {:order-id "ord-1"} :column :amount :format {:display "currency" :currency "USD"}}) ``` Display formatting is render-only: - column `:format` metadata and sparse `:update-cell-format` overrides can render `relative-time`, `date`, `timestamp` / `date-time`, and `currency` - the web table preview and `Copy Markdown` apply those formats to the currently visible page - `:query`, `:get-row`, and CSV export keep canonical raw values Resource refs are also first-class cell values: - store canonical `{:type :resource-ref :uri ...}` maps in row data when a cell should point at another resource - the web table preview renders those cells as clickable resource chips and opens the target resource in the same panel or sidepeek - `Copy Markdown` uses the rendered label for the currently visible page - `:query`, `:get-row`, and CSV export keep the canonical raw resource-ref value Author one logical column: ```clojure (flow/step :table :define-order-summary {:op :set-column :table {:ref orders-ref} :column :order-summary :definition {:semantic-type :text :computed {:type :expr :expr {:op :concat :args [{:field :customer-name} " / " {:field :status}]}}}}) ``` `set-column` automatically recomputes existing rows for bounded tables. Use `:recompute` later only when you want to rerun derived/reference values after some other change. Dynamic enum columns: ```clojure (flow/step :table :define-status-enum {:op :set-column :table {:ref orders-ref} :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: - `:enum` implies `type-hint "enum"` - writes, `:update-cell`, CSV import, and `:recompute` normalize 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 row values, `:query`, `:get-row`, and CSV export keep the normalized ids - the web table preview and `Copy Markdown` render enum names instead of raw ids ## Result Shapes Representative responses: ```clojure ;; :query {:table-name "orders" :rows [{:order-id "ord-1" :status "open"}] :count 1 :page {:mode :offset :limit 25 :offset 0 :total-count 107 :has-more true :next-offset 25 :prev-offset nil}} ;; :query cursor page {:table-name "orders" :rows [{:order-id "ord-1" :status "open"}] :count 1 :page {:mode :cursor :limit 250 :page-size 1 :has-more true :next-cursor "..."}} ;; :aggregate {:results [{:currency "USD" :count 2 :total-amount 150.0}] :count 1} ;; :schema {:table-name "orders" :key-fields ["order-id"] :index-fields ["status" "customer-id"] :columns [...]} ;; :update-cell {:row {:order-id "ord-1" :status "closed"}} ;; :update-cell-format {:format {:display "currency" :currency "USD"}} ;; :set-column {:column {:column-name "order-summary" :semantic-type "text" :computed {:type "expr"}} :rows-updated 100 :auto-recomputed true} ;; :recompute {:rows-scanned 100 :rows-updated 100 :limit 1000 :offset 0} ;; :materialize-join {:table-name "orders-enriched" :rows-written 100 :join {:join-type :left :matched-rows 90 :unmatched-left-rows 10} :preflight {:left-row-count 100 :right-row-count 50 :right-duplicate-key-count 0 :destination-duplicate-key-count 0}} ``` ## CLI Pairing The CLI mirrors the shipped table operations: ```bash breyta resources read --limit 25 --offset 0 --partition-key month-2026-03 breyta resources table query --limit 25 --offset 0 --partition-keys month-2026-03,month-2026-04 breyta resources table query --page-mode cursor --limit 250 --sort-json '[["order-id","asc"]]' --partition-key month-2026-03 breyta resources table get-row --key order-id=ord-1 --partition-key month-2026-03 breyta resources table aggregate --group-by currency --metrics-json '[{"op":"count","where":[["status","=","open"]],"as":"open-count"},{"op":"sum","field":"amount","as":"total-amount"},{"op":"arg-max","field":"order-id","order-field":"amount","as":"largest-order-id"}]' --order-by-json '[["total-amount","desc"]]' --partition-key month-2026-03 breyta resources table aggregate --group-by-json '[{"field":"created-at","bucket":{"op":"date-trunc","unit":"month"},"as":"created-month"}]' --metrics-json '[{"op":"count","as":"count"},{"op":"collect-set","field":"currency","limit":5,"as":"currencies"}]' --having-json '[["count",">=",2]]' --order-by-json '[["created-month","asc"]]' breyta resources table aggregate --group-by-json '[{"field":"amount","bucket":{"op":"numeric-bin","size":10},"as":"amount-bin"}]' --metrics-json '[{"op":"count","as":"count"},{"op":"percentile","field":"amount","p":0.95,"as":"p95-amount"},{"op":"median","field":"amount","as":"median-amount"}]' --order-by-json '[["amount-bin","asc"]]' breyta resources table schema --partition-key month-2026-03 breyta resources table export --out orders.csv --partition-key month-2026-03 breyta resources table import --file orders.csv --write-mode append --partition-key month-2026-03 breyta resources table import orders-import --file orders.csv --write-mode upsert --key-fields order-id --index-fields status breyta resources table update-cell --key order-id=ord-1 --column status --value closed --partition-key month-2026-03 breyta resources table update-cell-format --key order-id=ord-1 --column amount --format-json '{"display":"currency","currency":"USD"}' --partition-key month-2026-03 breyta resources table set-column --column order-summary --semantic-type text --computed-json '{"type":"expr","expr":{"op":"concat","args":[{"field":"customer-name"}," / ",{"field":"status"}]}}' --partition-keys month-2026-03,month-2026-04 breyta resources table set-column --column status --enum-json '{"options":[{"id":"open","name":"Open","aliases":["OPEN","Open"]},{"id":"in-progress","name":"In progress","aliases":["IN_PROGRESS","In Progress"]}]}' --partition-keys month-2026-03,month-2026-04 breyta resources table recompute --limit 1000 --offset 0 --partition-key month-2026-03 breyta resources table materialize-join --left-json '{"table":{"ref":"res://...orders"}}' --right-json '{"table":{"ref":"res://...customers"}}' --on-json '[{"left-field":"customer-id","right-field":"customer-id"}]' --project-json '[{"field":"name","as":"customer-name"}]' --into-json '{"table":"joined-orders","write-mode":"upsert","key-fields":["order-id"]}' ``` ## Related - [Persisted Results And Resources](/docs/guide-persisted-results-and-resources) - [CLI Commands](/docs/reference-cli-commands) - [Flow Features](/docs/guide-flow-features-and-config-toggles) --- Document: Step Sleep URL: https://flows.breyta.ai/docs/reference-step-sleep.md HTML: https://flows.breyta.ai/docs/reference-step-sleep Last updated: 2026-05-16T07:04:10+02:00 # Step Sleep (`:sleep`) ## Quick Answer Use `:sleep` to pause a flow without blocking workers. Provide exactly one duration field: `:seconds`, `:millis`, or `:duration`. Use `:sleep` for fixed in-run delays such as deployment-lag buffers, polling gaps, or rate-limit spacing. Do not use `:wait` as a timer; waits are for external signals, webhooks, CLI actions, or human actions. ## Canonical Shape | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:type` | keyword | Yes | Must be `:sleep` | | `:seconds` | int | One-of | Integer seconds | | `:millis` | int | One-of | Integer milliseconds | | `:duration` | string | One-of | Duration string (for example `"5m"`, `"2h"`, `"1d"`) | | `:timeout` | int | No | Common step timeout option | | `:retry` | map | No | Common step retry option | ## Limits And Behavior - Exactly one of `:seconds`, `:millis`, or `:duration` can be set. - If none is set, sleep defaults to `1` second. - Maximum duration is constrained by the workflow max-duration limit. - In Temporal-backed runs, `:sleep` uses a workflow timer and is still captured as a normal step for run inspection. ## Result Shape ```edn {:slept-seconds 30} ``` ## Canonical Example ```clojure {:functions [{:id :retry-delay :language :clojure :code "(fn [input]\n {:seconds (min 300 (max 1 (* 5 (or (:attempt input) 1))))})"}] :flow '(let [delay (flow/step :function :compute-delay {:ref :retry-delay :input (flow/input)})] (flow/step :sleep :backoff {:seconds (:seconds delay)}))} ``` ## Related - [Limits And Recovery](/docs/reference-limits-and-recovery) - [Orchestration Constructs](/docs/reference-orchestration-constructs) - [Failure-Mode Cookbook](/docs/guide-failure-mode-cookbook) --- Document: Step SSH URL: https://flows.breyta.ai/docs/reference-step-ssh.md HTML: https://flows.breyta.ai/docs/reference-step-ssh Last updated: 2026-05-07T21:40:28+02:00 # Step SSH (`:ssh`) ## Quick Answer Use this reference for running remote commands over SSH. - **Sync**: the flow pauses until the remote command exits (or times out). - **Async agent pattern**: kick off a long-running job over SSH, then use a `:wait` callback to resume later (see [Remote Agents (SSH)](/docs/guide-remote-agents-ssh)). - **VM setup**: if you need to provision the host and wire secrets from scratch, start with [Set Up A VPS Or VM For Breyta SSH](/docs/guide-set-up-vps-vm-for-ssh). - **Important**: treat sync `:ssh` as short-task execution. For long-running or output-silent jobs, use async kickoff + callback wait. - **Testing**: local mock mode + real-SSH walkthrough (see [SSH Testing](/docs/guide-ssh-testing)). ## Canonical Shape Core fields: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:connection` | keyword/string | Yes | Slot name (preferred) or connection id | | `:command` | string | Yes | Shell command executed via `sh -lc` on the remote host | | `:workdir` | string | No | Runs `cd && ...` before the command | | `:env` | map | No | Environment variables passed to the command | | `:stdin` | string | No | Bytes written to the remote process stdin | | `:pty?` | boolean | No | Allocate a PTY (for interactive commands/tools) | | `:timeout` | int | No | Command timeout seconds (default 60) | | `:connect-timeout-ms` | int | No | Connection timeout in milliseconds | | `:max-output-bytes` | int | No | Combined stdout+stderr cap (bounded by server limits) | | `:ok-exit-codes` | vector | No | Allowed exit codes (default `[0]`) | | `:log-command-preview?` | boolean | No | If true, logs a short command preview (avoid secrets) | Success result (when exit code is allowed): ```clojure {:success true :exit 0 :stdout "..." :stderr "..." :duration-ms 123} ``` If the command exits with a disallowed exit code, the step **fails** and surfaces a structured error including the exit code and a stderr preview. ## SSH Connection (`:type ssh`, `:backend ssh`) The `:ssh` step expects an SSH connection with connection config like: ```json { "host": "example.com", "port": 22, "username": "ubuntu", "auth": { "type": "private-key", "secret-ref": "ssh-private-key", "passphrase-secret-ref": "ssh-key-passphrase" }, "known-hosts": { "secret-ref": "ssh-known-hosts" } } ``` Auth: - `auth.type` supports `private-key` and `password`. - `secret-ref` values are fetched from the workspace secret-store at runtime. Host key verification: - Prefer `known-hosts` (a secret containing OpenSSH `known_hosts` content). - Dev-only escape hatch: set `insecure-skip-host-key-verification: true` on the connection config. Local simulation (no network): - Set `mode: "mock"` on the connection config. The step returns a deterministic mock result. ## Limits And Behavior - The remote command is executed as: `sh -lc "