# 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-25T12:13:11+02:00 # Start Here Use this page to choose the right Breyta surface. The workspace MCP server and the CLI are both supported agent surfaces: MCP is useful when the host can call tools and resources directly, while the CLI is useful for terminal, CI, local project, and operator workflows. ## Choose Your Path | You are... | Start with | Why | |---|---|---| | Connecting Codex, Claude Code, Cursor, VS Code Copilot, opencode, or another MCP-capable agent | [Connect Breyta MCP To Agentic Coding Tools](/docs/mcp-client-setup), then [Workspace MCP](/docs/mcp-workspace) | Gives the agent direct MCP tools, resources, prompts, flow-tool discovery, setup checks, definition pull, run debugging, release after approval, and feedback. | | Building from a terminal, CI job, or local operator workflow | [CLI Essentials](/docs/cli-essentials), then [CLI Workflow](/docs/guide-cli-workflow) | Gives exact commands for draft source, configuration, run, release, and install operations. | | Creating or editing flow definitions | [Author Flows](/docs/playbook-author-flows); MCP clients can also read [MCP Flow Authoring](/docs/mcp-flow-authoring) | Explains definition-first flow design, resource refs, validation, draft creation, and approval-gated release. | | Debugging runs or outputs | [Debug And Verify](/docs/playbook-debug-and-verify) or MCP prompt `debug_failed_run` | Keeps run inspection focused instead of loading full logs or broad references. | | Looking up a primitive, schema, or broader docs map | [Reference Index](/docs/reference-index) | Routes to the exact reference page without making the first path CLI-heavy. | ## MCP Path When MCP is the chosen connection path, use the MCP-specific docs instead of translating CLI examples: - [Connect Breyta MCP To Agentic Coding Tools](/docs/mcp-client-setup) shows setup for popular coding agents. - [Workspace MCP](/docs/mcp-workspace) explains the workspace endpoint, scopes, toolsets, flow-tool discovery, source-definition resources, prompts, and feedback. - [MCP Flow Authoring](/docs/mcp-flow-authoring) explains source pull through `get_flow_definition`, draft creation through `validate_flow_definition`/`create_flow_draft`, and approval-gated `release_flow`. - [MCP Tool Safety](/docs/mcp-tool-safety) explains capability gates, exposure headers, secrets, errors, and feedback. Inside an MCP client, read `breyta://mcp-docs/overview`, then call `get_workspace_context`, `search_flow_tools`, or the relevant prompt workflow. Use the CLI docs for deeper flow language reference or operator tasks that are not exposed through the workspace MCP server. ## CLI Quickstart Use this sequence when you are working from a terminal or asking an agent to run local CLI commands: ```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: Workspace MCP URL: https://flows.breyta.ai/docs/mcp-workspace.md HTML: https://flows.breyta.ai/docs/mcp-workspace Last updated: 2026-07-21T19:03:55+02:00 # Breyta Workspace MCP The workspace MCP server is an agent-facing control plane for one Breyta workspace. It exposes a small set of intent-shaped tools instead of mirroring every Breyta HTTP or CLI command. ## New User Setup Set up one MCP server entry per workspace. That single workspace server can search, inspect, and call accessible workspace flows and installed public-flow installations that define `:interfaces :mcp`; users do not need one MCP server entry per flow unless they deliberately want per-flow isolation. 1. Choose the workspace id. The production endpoint shape is: ```text https://flows.breyta.ai/api/workspaces/{workspace_id}/mcp ``` 2. Choose an authenticated Breyta bearer token for the coding agent. The token can belong to any Breyta principal with the needed capabilities. A service account is not required when the MCP host already has a suitable user or workspace bearer token. A scoped service-account key is recommended for unattended or shared agent setups because it can be narrowed and rotated independently from a human session. For an optional read-only service-account client that should discover flows, inspect MCP interfaces, inventory connections, read step references, read run status, and report feedback: ```bash breyta service-accounts create \ --name workspace-mcp-agent \ --scope flows.read \ --scope runs.read \ --scope resources.read \ --scope installations.read \ --scope connections.read \ --scope steps.read \ --scope feedback.send ``` For any bearer token, the authenticated principal needs equivalent capabilities. Add `flows.run` only when the MCP client should expose the `run` toolset or call flow MCP interfaces through `call_flow_tool`. Keep `installations.read` when the client should discover or call installed public-flow tools. Add `flows.manage` only when the MCP client should create/update drafts through `create_flow_draft` or release approved versions through `release_flow`. Avoid `workspace.full` for normal MCP clients. 3. If using a service account, mint an API key. ```bash breyta service-accounts keys create --name coding-agent ``` The returned `apiKey` is shown once. Store whichever bearer token you use in the MCP host's secret input, local user config, or environment, depending on the client. 4. Add an HTTP or Streamable HTTP MCP server to the coding agent config. Example for workspace `ws-acme`: ```json { "servers": { "breyta-ws-acme": { "type": "http", "url": "https://flows.breyta.ai/api/workspaces/ws-acme/mcp", "headers": { "Authorization": "Bearer ${input:breyta_mcp_token}" } } }, "inputs": [ { "type": "promptString", "id": "breyta_mcp_token", "description": "Breyta MCP bearer token", "password": true } ] } ``` If the host uses a different config wrapper, keep the same server type, URL, and authorization header. For client-specific setup snippets for Codex, Claude Code, Cursor, Windsurf, VS Code Copilot, Cline, Roo Code, Continue, Gemini CLI, opencode, Goose, and Zed, use [Connect Breyta MCP To Agentic Coding Tools](/docs/mcp-client-setup). If the client or ACP-compatible host only supports stdio MCP servers, use the Breyta CLI proxy instead: ```bash export BREYTA_MCP_TOKEN="" breyta mcp config \ --workspace-id ws-acme \ --provider codex \ --transport stdio \ --read-only \ --toolsets read,setup,debug,feedback ``` `breyta mcp init` is an alias for the same config-rendering setup path. Generated stdio configs call: ```bash breyta mcp stdio \ --workspace-id ws-acme \ --token-env-var BREYTA_MCP_TOKEN \ --read-only \ --toolsets read,setup,feedback ``` Do not put access tokens in query strings or flow inputs. Use an authorization header or the host application's secret input mechanism. The workspace endpoint is Streamable HTTP over `POST`. MCP hosts and gateways must send JSON-RPC requests with `Content-Type: application/json` and an `Accept` header that allows both `application/json` and `text/event-stream`. `GET` currently returns a method-not-allowed response because this endpoint does not offer a standalone server-sent event stream. Breyta accepts the common negotiated MCP protocol versions `2025-11-25`, `2025-06-18`, and `2025-03-26`. The server responds with the client's requested version when it is supported, otherwise with the latest supported version. Use HTTP or Streamable HTTP mode in clients; do not configure the workspace endpoint as legacy SSE. ## Registry And Directory Metadata Breyta publishes Workspace MCP as one workspace-scoped remote server, not as one directory entry per flow. The registry name is: ```text ai.breyta/workspace-mcp ``` The official MCP Registry manifest lives in the Breyta CLI source as `server.json`. It describes the hosted Streamable HTTP endpoint with a required `workspace_id` URL variable and an `Authorization` header supplied by the user or client secret store. Directory scanners that cannot authenticate against a workspace endpoint can read static metadata from: ```text https://flows.breyta.ai/.well-known/mcp/server-card.json ``` That server card lists the same public tool names, descriptions, schemas, resources, prompts, and safety annotations as the authenticated workspace MCP surface. If a directory requires OAuth for authenticated remote MCP servers, treat that as a separate reviewed-listing path; the bearer-token setup described here remains the default direct user and service-account setup path. Use the same URL and header shape in VS Code, Cursor, Claude, Codex-compatible gateways, or any other host that supports HTTP MCP servers. If a host uses a different config wrapper, keep these fields equivalent: | Field | Value | | --- | --- | | Server type | HTTP or Streamable HTTP MCP, or CLI stdio proxy for clients without HTTP MCP | | URL | `https://flows.breyta.ai/api/workspaces/{workspace_id}/mcp` | | Auth | `Authorization: Bearer ` | | Optional narrowing | `X-MCP-Toolsets`, `X-MCP-Tools`, `X-MCP-Exclude-Tools`, or `X-MCP-Readonly` headers | Configure one named server per workspace when a user needs access to multiple workspaces. For stdio proxy verification, run `breyta mcp doctor --workspace-id --token-env-var BREYTA_MCP_TOKEN` with the same `--read-only`, `--toolsets`, `--tools`, or `--exclude-tools` narrowing flags used in the generated config. The doctor calls `initialize` and `tools/list`; it does not invoke mutating tools. If a tool call is rejected after setup, inspect `structuredContent.details.blockers` in the tool result. `missingCapabilities` means the authenticated principal lacks Breyta capabilities such as `flows.run`, `connections.read`, or `flows.manage`; `toolset_policy`, `read_only_policy`, `include_tools_policy`, and `exclude_tools_policy` mean the MCP server entry is narrowing the visible tool surface. Adjust scopes and MCP narrowing separately. ## First Connection Checklist When setting up a new client, verify the protocol surface before asking an agent to do real work: 1. Connect and call `initialize`. 2. Call `resources/list` and read `breyta://mcp-docs/overview`. 3. Call `resources/templates/list` and confirm the expected flow and run URI templates are visible. 4. Call `prompts/list` and inspect any workflow prompt the user wants to run. 5. Call `tools/list` with the intended narrowing headers. 6. Call `get_workspace_context` and confirm the reported workspace, toolsets, and capability groups. 7. Call `list_connections` and `check_connections` when the user expects the agent to build or debug flows that touch external systems. 8. Use `search_flow_tools` or `search_flows` before calling any flow tool. 9. Use `get_flow_definition`, `get_step_reference`, `check_flow_readiness`, and `inspect_run_step` for focused build/debug context instead of loading broad CLI docs or full run logs. 10. Call `send_feedback` if setup, docs, discovery, or invocation behavior is confusing. The MCP Inspector is useful for this smoke test because it shows resources, prompts, tool schemas, and tool execution results in one place. For HTTP MCP, configure the Inspector with the workspace endpoint and the same bearer token header the real client will use. ## MCP Documentation Surface Breyta exposes MCP-specific docs as MCP resources so agents do not have to infer MCP behavior from CLI-oriented pages: | Resource | Use | | --- | --- | | `breyta://mcp-docs/overview` | Workspace setup, discovery, and brokered flow tool usage. | | `breyta://mcp-docs/client-setup` | Client-specific setup snippets for popular agentic coding tools. | | `breyta://mcp-docs/flow-authoring` | Flow definition pull, draft creation, validation, and approval-gated release through MCP tools. | | `breyta://mcp-docs/tool-safety` | Capability gates, human approval boundaries, secrets, and feedback. | These resources are listed with MCP resource annotations so clients can prioritize them during context selection. `breyta://mcp-docs/{doc_id}` is also available as a template for direct reads. Use MCP docs for MCP behavior and the normal Breyta docs for deeper flow language reference: | Need | MCP action | | --- | --- | | Understand this server | Read `breyta://mcp-docs/overview`. | | Author a new draft | Get prompt `create_flow_from_goal`, read `breyta://mcp-docs/flow-authoring`, then call `validate_flow_definition` before `create_flow_draft`. | | Edit an existing flow | Call `get_flow_definition` for `draft`, `live`, or omit `source` and pass a specific `version`, edit the returned `flowLiteral`, then validate and save a new draft. | | Review a draft | Get prompt `review_flow_definition`, call `get_flow_definition` and `validate_flow_definition`, and inspect relevant flow resources. | | Release after approval | Call `check_flow_readiness`, summarize risks, then call `release_flow` only after the user approves the flow/version. Pull `get_flow_definition` with `source: "live"` afterward. | | Debug a run | Get prompt `debug_failed_run`, read `breyta://runs/{run_id}` and `breyta://runs/{run_id}/events`, then call `get_run_status` and `inspect_run_step` with focused flags. `inspect_run_step` returns the selected step plus a run step summary, not unrelated step outputs. Set `include_step_results: true` only when you need the selected step's captured result preview. | | Check setup | Call `list_connections`, `check_connections`, and `check_flow_readiness` before running setup-sensitive flows. | | Understand a primitive | Call `get_step_reference` for the specific step id before guessing parameters. | | Report friction | Call `send_feedback` with redacted context and the relevant `resource_uri`, `run_id`, or `flow_slug`. | ## MCP And CLI Boundaries Use MCP when the agent needs to discover flows, inspect callable MCP interfaces, check setup, call a flow tool, debug a run, validate a candidate definition, create a draft, pull source definitions, release an approved version, or send feedback. Use the CLI, UI, or existing operator workflows for credential creation, connection or secret mutation, marketplace or billing changes, destructive run control, broad admin work, and local file/project setup. These operations are intentionally outside the default workspace MCP tool list. ## Capability Map | User goal | MCP surface | Use another Breyta surface when | | --- | --- | --- | | Confirm setup | `get_workspace_context`, `resources/list`, `tools/list`, `breyta mcp doctor` for stdio proxy checks | The bearer token needs to be created, rotated, or granted additional capabilities. | | Discover callable flow tools | `search_flow_tools`, `inspect_flow_tool`, `breyta://flows/{slug}/mcp-tools/{tool_name}` | The user wants to install, publish, or change marketplace visibility. | | Search broader workspace flows | `search_flows`, flow resources, MCP docs resources | The agent needs full local project or source-file editing workflows. | | Check external setup | `list_connections`, `check_connections`, `check_flow_readiness` | Credentials, secrets, or connection records need to be created or changed. | | Pull or review source | `get_flow_definition`, `breyta://flows/{slug}/definition/{source}`, `breyta://flows/{slug}/versions/{version}/definition` | The user needs local filesystem checkout behavior, bulk source export, or unsupported CLI-only transforms. | | Build a draft | `get_flow_definition`, `validate_flow_definition`, `create_flow_draft`, `get_step_reference` | The draft needs public listing, marketplace, billing, secrets, or connection mutation. | | Release an approved flow | `release_flow`, then `get_flow_definition` with `source: "live"` | The user needs marketplace/public/billing changes or nonstandard rollout operations. | | Run and debug a flow tool | `call_flow_tool`, `get_run_status`, `inspect_run_step`, run resources | The user needs destructive run control, replay, or approval workflows outside the declared MCP interface. | | Report friction | `send_feedback` | The report contains secrets or sensitive customer data; redact it first. | ## Scope Bundles Prefer narrow Breyta capabilities: | Bundle | Capabilities | Use | | --- | --- | --- | | Read/search | `flows.read`, `resources.read`, `installations.read` | Discover flows, installed flows, MCP interfaces, docs, and resource metadata. | | Setup/reference | Read/search plus `connections.read`, `steps.read` | Inspect available connections, check connection health, and fetch focused step docs/examples. | | Run/debug | Read/search plus `flows.run`, `runs.read` | Start flow MCP interfaces, inspect run status/events, and inspect a specific run step. | | Draft authoring | Read/search plus `flows.manage` | Create/update draft flow definitions and validate drafts. | | Release | Read/search plus `flows.manage` | Release/promote an approved flow version to the workspace live target. | | Feedback | `feedback.send` | Report MCP docs, setup, runtime, or flow-interface friction. | Avoid `workspace.full` for normal MCP clients. ## Common Server Presets These presets combine bearer-token capabilities with MCP narrowing headers. Headers only narrow the visible MCP surface; they do not grant permission without matching Breyta capabilities. | Preset | Breyta capabilities | Narrowing headers | Use | | --- | --- | --- | --- | | Read-only discovery | `flows.read`, `resources.read`, `installations.read`, `feedback.send` | `X-MCP-Readonly: true`, `X-MCP-Toolsets: read,feedback` | Search flows and flow tools, read MCP docs/resources, and report setup friction. | | Setup and debugging | Read-only discovery plus `runs.read`, `connections.read`, `steps.read` | `X-MCP-Readonly: true`, `X-MCP-Toolsets: read,setup,debug,feedback` | Inspect connections, readiness, step references, run status, events, and step IO without starting new runs. | | Run approved flow tools | Setup and debugging plus `flows.run` | `X-MCP-Toolsets: read,setup,run,debug,feedback` | Let the agent start runs through declared flow `:interfaces :mcp` and debug the result. | | Draft authoring | Setup and debugging plus `flows.manage` | `X-MCP-Toolsets: read,setup,authoring,debug,feedback` | Let the agent validate supplied definitions and create/update drafts. | | Approved release | Draft authoring plus `flows.manage` | `X-MCP-Toolsets: read,setup,authoring,release,debug,feedback` | Let the agent release/promote a specific approved version after readiness checks. | ## Tool Exposure Controls The default server advertises the workspace MCP surface that the current principal is allowed to use. For bearer tokens, tools outside the authenticated principal's granted capabilities are hidden from `tools/list` and rejected at `tools/call`; resource templates, prompt workflows, and resource reads are scoped the same way. Exposure headers can narrow the tool set but cannot widen it. Clients or gateways can narrow the exposed tools with headers: | Header | Example | Effect | | --- | --- | --- | | `X-MCP-Toolsets` | `read,feedback` | Include tools in selected toolsets. | | `X-MCP-Tools` | `search_flows,get_run_status` | Include only named tools. | | `X-MCP-Exclude-Tools` | `create_flow_draft,release_flow` | Hide named tools. | | `X-MCP-Readonly` | `true` | Hide tools that run flows, write drafts, or release flows; `send_feedback` remains available for support sessions when allowed by token scope. | `tools/list` also accepts matching request params for discovery experiments, but call-time enforcement should use headers so the same policy applies to `tools/call`. ## Aggregate Flow Brokering The workspace endpoint can broker all accessible flow `:interfaces :mcp` tools: 1. Call `search_flow_tools` when the goal is to find callable flow tools. 2. Call `search_flows` for broader flow/template/install discovery when the user is still choosing a flow. 3. Call `inspect_flow_tool` for the selected flow and tool. 4. Call `list_connections`, `check_connections`, or `check_flow_readiness` when setup or external systems matter. 5. Call `call_flow_tool` with `flow_slug`, `tool_name`, and an `input` object containing the selected flow tool's input fields. Include `installation_id` when targeting a specific installed public app instance. 6. Poll `get_run_status`, inspect specific steps with `inspect_run_step`, or read `breyta://runs/{run_id}` and `breyta://runs/{run_id}/events`. The workspace server exposes a stable, compact tool list. Flow-specific MCP interfaces are discovered through `search_flow_tools` and invoked through `call_flow_tool`, so large workspaces do not need one listed MCP tool per flow. `search_flow_tools` returns concise entries for callable flow interfaces and supports text search, browsing, and exact tool-name lookup. Discovery results are advisory: before a run starts, `call_flow_tool` loads the selected flow and verifies that the requested MCP interface is still available. Public apps that require setup, billing, or installer-owned connections should be installed, configured, and enabled before an agent invokes them. Do not treat the workspace MCP broker as a way to run arbitrary public Discover flows before installation. Users do not need to install one MCP server per flow unless they want a direct per-flow endpoint. ## Tool Catalog The workspace surface is intentionally small and intent-shaped: MCP tool argument names use `snake_case`, matching Breyta's MCP tool names. Examples: `flow_slug`, `tool_name`, `run_id`, `workflow_id`, `flow_literal`, `connection_ids`, and `include_events`. MCP tools are grouped around agent workflows rather than individual CLI commands. For example, use `check_flow_readiness` for setup/readiness context, and use `search_flow_tools`, `inspect_flow_tool`, and `call_flow_tool` for flow MCP interfaces. Use the CLI docs for terminal/operator tasks that are not available through this MCP server. | Tool | Toolset | Capability gate | Use | | --- | --- | --- | --- | | `get_workspace_context` | `context`, `read` | none | Confirm server context, available docs, limits, toolsets, and capability groups. | | `search_flows` | `read`, `search` | `flows.read` | Search workspace flows, installed flows, and selected templates. | | `search_flow_tools` | `read`, `search`, `run` | `flows.read` | Search callable flow MCP interfaces. | | `list_connections` | `read`, `setup` | `connections.read` | List redacted workspace connections and local setup health. | | `check_connections` | `read`, `setup` | `connections.read` | Check selected connections or a bounded page of all connections without exposing credentials. | | `inspect_flow_tool` | `read`, `search`, `run`, `authoring` | `flows.read` | Inspect one flow MCP interface before calling it. | | `call_flow_tool` | `run` | `flows.read`, `flows.run` | Start a run through a selected flow MCP interface; pass `installation_id` for a specific installed app instance. | | `get_run_status` | `read`, `run`, `debug` | `runs.read` | Poll run state, bounded events, and optional result metadata. | | `inspect_run_step` | `read`, `run`, `debug` | `runs.read` | Inspect one step from a run with bounded/redacted step data, a compact `stepsSummary`, and optional step events. | | `check_flow_readiness` | `read`, `setup`, `authoring` | `flows.read` | Run existing doctor, configure-check, and public preflight checks. | | `get_step_reference` | `read`, `authoring` | `steps.read` | Fetch focused step docs, examples, and optional artifact metadata. | | `send_feedback` | `feedback` | `feedback.send` | Report MCP docs, setup, runtime, or flow-interface friction. | | `validate_flow_definition` | `read`, `authoring` | `flows.read` | Validate a supplied EDN literal or existing draft/live flow. | | `get_flow_definition` | `read`, `authoring`, `debug` | `flows.read` | Pull full draft/latest/live or versioned source definitions for authoring and debugging. | | `create_flow_draft` | `authoring` | `flows.manage` | Create or update a draft from a supplied EDN literal. | | `release_flow` | `release` | `flows.manage` | Release/promote an approved latest pushed version or selected version to the workspace live target. | Tool execution errors, hidden-tool rejections, and tool argument validation failures are returned as tool results with `isError`. Protocol errors such as malformed JSON-RPC envelopes, unsupported methods, and unknown MCP tool names use JSON-RPC errors. Treat `call_flow_tool` as long-running work: it starts the backing run and returns run identifiers and resource links for follow-up reads. For complete report-style outputs, prefer reading the returned `resultResource` with `resources/read` instead of relying on inline tool text. Textual result payloads are served from `breyta://runs/{run_id}/result` with the most specific safe MIME type Breyta can determine, such as `text/html` for HTML reports, so clients such as Claude Desktop can load the full document without clipping it into the tool response. Result resources also apply credential-focused redaction before returning textual or JSON content. Breyta viewer envelopes such as `{:breyta.viewer/kind :html :breyta.viewer/value "..."}` are unwrapped to their native textual MIME type when read through the result resource. Typed textual result reads also include an embedded MCP resource content block for clients that render artifacts from `content` blocks instead of only `resources/read` `contents`. ## Resources And Prompts Direct resources: | Resource | Capability gate | Notes | | --- | --- | --- | | `breyta://workspaces/{workspace_id}/mcp` | none | Current workspace MCP context. | | `breyta://mcp-docs/overview` | none | Main MCP docs entry point. | | `breyta://mcp-docs/client-setup` | none | Client-specific setup snippets for popular coding agents. | | `breyta://mcp-docs/flow-authoring` | none | MCP flow authoring workflow. | | `breyta://mcp-docs/tool-safety` | none | MCP safety rules. | Resource templates: | Template | Capability gate | Notes | | --- | --- | --- | | `breyta://flows/{slug}` | `flows.read` | Flow definition or installed public flow metadata. | | `breyta://flows/{slug}/definition/{source}` | `flows.read` | Full source literal for `draft`, `latest`, `live`, or `active`, returned as top-level `flowLiteral`. | | `breyta://flows/{slug}/versions/{version}/definition` | `flows.read` | Full source literal for a specific released version, returned as top-level `flowLiteral`. | | `breyta://flows/{slug}/mcp-tools/{tool_name}` | `flows.read` | One flow MCP interface descriptor. | | `breyta://runs/{run_id}` | `runs.read` | Run status and summary. | | `breyta://runs/{run_id}/result` | `runs.read` | Full final run result. Explicit textual MIME types are preserved, top-level Breyta viewer envelopes are unwrapped, HTML reports are served as `text/html`, and result content is redacted before return. | | `breyta://runs/{run_id}/events` | `runs.read` | Bounded event history. | | `breyta://mcp-docs/{doc_id}` | none | Curated MCP-specific docs. | Prompts are user-invoked workflows, not hidden automatic behavior: | Prompt | Capability gate | Use | | --- | --- | --- | | `create_flow_from_goal` | `flows.read`, `flows.manage` | Guide MCP draft creation from a user goal. | | `review_flow_definition` | `flows.read` | Review a draft flow with validation and setup checks. | | `debug_failed_run` | `runs.read` | Inspect run resources and summarize likely causes. | | `publish_flow_safely` | `flows.read`, `flows.manage` | Prepare release/publish work while keeping approval gates explicit. | ## Feedback If an agent hits unclear docs, setup friction, runtime errors, or a confusing flow MCP interface, call `send_feedback` with a short summary and redacted context. Prefer linking `resource_uri`, `run_id`, or `flow_slug` instead of embedding raw private payloads. ## Auth Model This endpoint uses existing Breyta API authentication, including service-account tokens. Do not describe it as MCP OAuth-complete or rely on OAuth discovery for access. --- Document: MCP Flow Authoring URL: https://flows.breyta.ai/docs/mcp-flow-authoring.md HTML: https://flows.breyta.ai/docs/mcp-flow-authoring Last updated: 2026-05-27T12:43:45+02:00 # Breyta MCP Flow Authoring Authoring through MCP stays draft-scoped until a user explicitly approves release or publishing. Recommended workflow: 1. Read `breyta://mcp-docs/overview` and this document. 2. Get prompt `create_flow_from_goal` when the user is asking for a new flow. 3. Use `search_flows` for examples and nearby workspace flows. 4. Call `list_connections` and `check_connections` before designing steps that depend on external systems. 5. Call `get_step_reference` for the exact primitive you plan to use. 6. When editing an existing flow, call `get_flow_definition` for the exact source: `draft` for current authoring state, `live` for released behavior, or omit `source` and pass `version` for a specific released version. 7. Read focused flow language docs only when needed; do not load the full CLI docs into context by default. 8. Draft or edit an EDN `flow_literal`. 9. Call `validate_flow_definition` with the `flow_literal`. 10. Call `create_flow_draft` only after validation errors are understood. 11. Call `check_flow_readiness` before running setup-sensitive drafts. 12. Run a draft invocation through a flow MCP interface or existing run tooling. 13. Use `inspect_run_step` when a run fails or a step output shape is unclear. 14. After explicit user approval, call `release_flow` to release/promote the selected version to live. 15. Pull `get_flow_definition` with `source: "live"` and smoke-test the live or install-shaped path before calling the work released. 16. Keep publish, billing, and setup mutation behind explicit user approval and non-MCP workflows unless a supported MCP tool exists. MCP docs are not CLI transcripts. They should describe MCP tool calls, required arguments, resource URIs, safety checks, and the next MCP action an agent should take. `create_flow_draft` accepts a supplied EDN flow literal. It does not generate a flow from a goal server-side and does not release or publish. `validate_flow_definition` accepts either a supplied EDN flow literal or an existing `flow_slug` plus source/mode. It returns diagnostics without publishing. `get_flow_definition` pulls the full source literal for `draft`, `latest`, `live`/`active`, or a specific `version`. Use `source` for named source reads, or omit `source` and pass `version` for a specific released version. Use it before modifying an existing flow and again after release when you need to confirm the live definition. The response includes the editable source as the top-level `flowLiteral`. `release_flow` releases the supplied `version`, or the latest pushed version when `version` is omitted, and promotes it to the workspace live target. It is in the `release` toolset, requires `flows.manage`, and should only be called after the user approves the specific flow and version. It does not accept deploy keys; deploy-key-guarded flows should be released through an operator workflow that can handle the secret outside MCP context. ## MCP Authoring Contract Use this shape for agent-authored work: | Stage | MCP primitive | Expected behavior | | --- | --- | --- | | Discover | `resources/list`, `prompts/list`, `search_flows` | Gather only the context needed for the task. | | Inventory setup | `list_connections`, `check_connections` | Reuse existing workspace connections and catch missing credentials before authoring around them. | | Inspect primitive | `get_step_reference` | Fetch exact step docs/examples for the primitive being used. | | Draft | local reasoning plus `flow_literal` | Produce a complete EDN flow definition with explicit `:slug`, `:name`, `:concurrency`, `:invocations`, and appropriate `:interfaces`. | | Validate | `validate_flow_definition` | Fix diagnostics before creating or updating a draft. | | Pull definition | `get_flow_definition`, `breyta://flows/{slug}/definition/{source}`, `breyta://flows/{slug}/versions/{version}/definition` | Pull exact draft/live/latest or versioned source before editing, reviewing, or debugging definition drift. | | Save draft | `create_flow_draft` | Write draft state only. Do not release or publish. | | Check setup | `check_flow_readiness` | Run existing doctor, configuration, and preflight checks before claiming readiness. | | Exercise | `inspect_flow_tool`, `call_flow_tool`, `get_run_status`, `inspect_run_step` | Prefer draft/source-specific testing and focused step inspection when the user is still iterating. | | Release | `release_flow` | Release/promote only after explicit user approval, then verify live definition and behavior. | | Report friction | `send_feedback` | Send redacted docs/runtime/interface feedback when the MCP path is unclear or blocked. | ## Authoring Rules For Flow MCP Interfaces When a flow is meant to be reusable by other agents, expose a small `:interfaces :mcp` surface over a clear invocation contract: - Give each MCP tool a stable `:tool-name` in the flow definition; call it through MCP as `tool_name`. - A flow may expose multiple MCP tools when they are related operations on the same solution; keep tool names unique. - Keep per-run arguments in `:invocations`, not in `:requires`. - Use invocation input `:type :json` for object or array payloads that agents pass through `call_flow_tool` as `input`. - Put one-time setup, connections, secrets, and installer-owned config in `:requires`. - Write tool descriptions for humans and search. They are hidden from agents by default. - Prefer structured output and resource refs over large inline blobs. - Make destructive or externally visible actions explicit in the tool name and description. - Validate and smoke-test the draft before claiming that the MCP interface is usable. Use `search_flow_tools` after saving a draft or release to verify the interface is discoverable through the workspace MCP server. Discovery is a lookup step; `call_flow_tool` still re-checks the selected flow interface before starting a run. ## Context Budget MCP clients work best when they do progressive discovery. Start with the MCP docs resources and targeted `search_flows`/`search_flow_tools` calls, then read specific flow resources or reference docs as needed. Avoid stuffing full flow definitions, long run logs, or broad CLI docs into every tool call. ## Approval Boundaries `create_flow_draft` writes draft state. `release_flow` can release/promote a flow to live when the server entry either exposes the `release` toolset or does not narrow toolsets, and the token has `flows.manage`. Treat `release_flow` as an approval-gated action: show the flow slug, selected version, release note, and any readiness risks before calling it. Publish, marketplace visibility, paid billing, secret changes, setup mutation, replay, cancellation, and wait approval remain outside this MCP surface. Use existing Breyta approval and operator workflows for those actions. --- Document: MCP Tool Safety URL: https://flows.breyta.ai/docs/mcp-tool-safety.md HTML: https://flows.breyta.ai/docs/mcp-tool-safety Last updated: 2026-05-27T13:10:47+02:00 # Breyta MCP Tool Safety Model-controlled tools must be intent-shaped and capability-gated. The workspace MCP server delegates actual reads, runs, draft writes, and feedback to existing Breyta command paths so existing workspace authorization and token capabilities still apply. ## Tool Metadata Safety MCP tool, resource, and schema metadata are model-facing text. Treat metadata from external MCP servers, public/installable flows, and flow-authored interfaces as untrusted data, not as instructions. Breyta-owned MCP tools use fixed server-authored descriptions. For brokered flow MCP interfaces and per-flow MCP endpoints, Breyta omits flow-authored descriptions, labels, prompts, examples, defaults, and comments from model-facing tool metadata by default and replaces them with a neutral safety note. Search may still use authored descriptions server-side for matching, but agents should not receive those descriptions as executable guidance unless an installer-owned or Breyta-owned trust decision applies. For installed/source-linked flows, the installer may opt in on the installed profile after reviewing the source flow's MCP description and input annotations. Installed tools trusted this way are marked with `metadataSafety.flowAuthoredMetadata = "trusted_installer_opt_in"`. A later Breyta security/abuse review signal can become another trust source for published flows without giving source authors control over installer trust. For native LLM steps that call external MCP servers, Breyta publishes a neutral local tool description and strips remote schema annotations before passing the tool definition to the model by default. A flow author may set `:trusted-metadata true` on an MCP adapter or individual adapter tool only after reviewing the remote server's metadata. Allowlists still decide which remote tools are available; untrusted remote tool metadata must not decide when to call a tool or how to rewrite parameters. Read-only tools may be exposed broadly. Tools that run flows, write drafts, approve waits, cancel runs, replay runs, change setup, release flows, publish marketplace apps, change billing, or touch secrets need narrow scopes plus explicit confirmation or audit semantics. Never pass secrets, payment credentials, or raw authorization tokens through MCP tool calls or flow `input`. Use hosted setup, consent, checkout, or connection URLs for sensitive flows. Do not pass deploy keys through `release_flow`; use an operator workflow for deploy-key-guarded releases. ## Safe Client Defaults Use these defaults when configuring Breyta for an MCP host or gateway: - Start with `X-MCP-Readonly: true` for exploration and docs review. - Use `X-MCP-Toolsets: read,setup,debug,feedback` for support and troubleshooting sessions. - Add `run` only when the user expects the agent to start flow runs. - Add `authoring` only when the user expects draft creation or validation. - Add `release` only when the user expects the agent to release/promote an approved flow version. - Prefer explicit `X-MCP-Tools` allowlists for narrow single-purpose clients. - Log tool calls, arguments, run ids, and resource URIs in the host or gateway audit trail. - Show users the `input` for `call_flow_tool`, the `flow_literal` for `create_flow_draft`, and the selected flow/version/release note for `release_flow` before execution when the host supports approvals. Exposure headers narrow the visible tool set. They do not grant permissions. The bearer token's principal still needs the matching Breyta capability. Read-only exposure blocks flow runs, draft writes, and releases but still allows `send_feedback` so support sessions can report setup or runtime friction. ## Error And Feedback Loop Use protocol errors to distinguish client/setup problems from flow runtime problems: | Symptom | Likely class | Next action | | --- | --- | --- | | `401` or `403` before JSON-RPC result | Auth, token, workspace, or Origin problem | Check endpoint path, bearer token, token workspace, and client Origin behavior. | | JSON-RPC `-32602` | Unknown/hidden tool, prompt, resource, or invalid arguments | Re-run `tools/list`, `resources/list`, or `prompts/list` under the same headers. | | Tool result with `isError` | Breyta command, validation, setup, billing, or run problem | Read the structured content, linked resources, and run status. | | Empty `search_flow_tools` result | No matching callable MCP interface or insufficient visibility | Try `search_flows`, inspect the flow, and verify it defines `:interfaces :mcp`. | | Missing or failing connection | Setup or credential problem | Call `list_connections`, then `check_connections` for the selected ids before editing or running the flow. | | Step output shape is unclear | Run-debugging problem | Call `inspect_run_step` for the specific run and step id. The response includes the selected step and `stepsSummary.availableStepIds`; it omits unrelated step outputs. Leave `include_step_results` off for timeline/status checks; set it to `true` only when you need that step's bounded captured result preview. | When a tool is hidden or rejected, Breyta returns structured `blockers` in the tool error details. Use the `reason` field to choose the repair: | Blocker reason | Meaning | Repair | | --- | --- | --- | | `missing_service_account_capabilities` | The authenticated principal or service-account key lacks one or more required Breyta capabilities. | Grant the listed `missingCapabilities` to the principal/key; if using a service account, mint/use a key from that account, then retry. | | `read_only_policy` | The MCP server entry has `--read-only` or `X-MCP-Readonly`, but the tool can mutate state. | Remove read-only only after the user approves mutating access for that server entry. | | `toolset_policy` | `--toolsets` or `X-MCP-Toolsets` does not include any toolset containing the requested tool. | Add one listed `toolToolsets` value, such as `run` for `call_flow_tool`, `authoring` for draft creation, or `release` for `release_flow`. | | `include_tools_policy` | `--tools` or `X-MCP-Tools` allowlists tools and omitted this one. | Add the tool name to the allowlist or remove the allowlist. | | `exclude_tools_policy` | `--exclude-tools` or `X-MCP-Exclude-Tools` explicitly hides the tool. | Remove the tool from the exclude list if the user wants it available. | If an agent encounters MCP friction, call `send_feedback` with: - `summary`: short issue title - `details`: what happened and expected behavior - `area`: `mcp`, `docs`, `runtime`, `flow-interface`, `setup`, or `other` - `resource_uri`, `run_id`, or `flow_slug` where available - redacted, bounded `context` Do not include raw headers, access tokens, API keys, OAuth codes, payment details, prompt-injected secrets, or full private payloads in feedback context. Run-control tools such as cancellation and wait approval are not exposed through the workspace MCP server. Use existing Breyta run-control workflows for those actions. --- Document: Connect Breyta MCP To Agentic Coding Tools URL: https://flows.breyta.ai/docs/mcp-client-setup.md HTML: https://flows.breyta.ai/docs/mcp-client-setup Last updated: 2026-06-02T23:22:28+02:00 # Connect Breyta MCP To Agentic Coding Tools Use this page when you want a coding agent to discover, inspect, build, debug, and run Breyta flows through the workspace MCP server. A common setup is one MCP server entry per Breyta workspace. That single workspace endpoint gives access to callable workspace flows and installed public-flow app instances that define `:interfaces :mcp`; use a separate per-flow server entry only when you want deliberate per-flow isolation. ## Prerequisites 1. Choose the Breyta workspace id. The production endpoint shape is: ```text https://flows.breyta.ai/api/workspaces/{workspace_id}/mcp ``` 2. Choose an authenticated Breyta bearer token for the agent. The token can belong to any Breyta principal with the needed capabilities. A service account is not required when the MCP host already has a suitable user or workspace bearer token. A scoped service-account key is recommended for unattended or shared agent setups because it can be narrowed and rotated independently from a human session. For an optional read-only service-account setup that can discover flows, inspect MCP interfaces, inventory connections, read step references, and report feedback: ```bash breyta service-accounts create \ --name workspace-mcp-agent \ --scope flows.read \ --scope runs.read \ --scope resources.read \ --scope installations.read \ --scope connections.read \ --scope steps.read \ --scope feedback.send ``` For any bearer token, the authenticated principal needs equivalent capabilities. Add `flows.run` only for MCP server entries that expose the `run` toolset or allow `call_flow_tool`. Keep `installations.read` when the agent should discover or call installed public-flow tools. Add `flows.manage` only when the agent should create or update drafts through `create_flow_draft` or release approved versions through `release_flow`. 3. If using a service account, mint an API key. ```bash breyta service-accounts keys create --name coding-agent ``` The returned `apiKey` is shown once. 4. Store the bearer token outside version control. Most examples below use `BREYTA_MCP_TOKEN`. If your client does not support environment interpolation for headers, use that client's local user config or secret prompt mechanism instead of committing the bearer token. ## Smoke Test After adding the server, ask the agent to: ```text Use the Breyta MCP server. List available resources, read breyta://mcp-docs/overview, call get_workspace_context, then search for callable flow tools. ``` Then verify: - If the client exposes MCP resources, `resources/list` includes `breyta://mcp-docs/overview` and `breyta://mcp-docs/client-setup`. - `tools/list` includes `search_flows`, `search_flow_tools`, `inspect_flow_tool`, `call_flow_tool`, `get_run_status`, `list_connections`, `check_connections`, `get_flow_definition`, and `send_feedback`, subject to token scopes and any narrowing headers. - `get_workspace_context` reports the expected workspace id. - `search_flow_tools` returns callable flow MCP interfaces when the workspace contains flows or installed public-flow apps with `:interfaces :mcp`. If the agent hits setup friction, ask it to call `send_feedback` with area `setup` or `mcp` and redacted context. ## Compatibility Notes Breyta's workspace MCP endpoint is a hosted Streamable HTTP MCP endpoint: - Use HTTP or Streamable HTTP mode, not legacy SSE mode. - The endpoint accepts JSON-RPC over `POST` and may return a normal JSON response instead of an SSE stream. - The endpoint returns `405 Method Not Allowed` for `GET`, which the MCP transport allows when the server does not offer a standalone server-sent event stream. - Clients must send `Content-Type: application/json` and an `Accept` header that allows both `application/json` and `text/event-stream`. - Breyta accepts the common negotiated protocol versions `2025-11-25`, `2025-06-18`, and `2025-03-26`. - Configure the `Authorization` header explicitly. Breyta does not rely on MCP OAuth discovery for bearer-token access. - Native desktop and CLI clients should not send a browser `Origin` header. Browser-origin requests are accepted only when the origin matches the endpoint host. Not every MCP host exposes every MCP primitive in the same UI. Zed currently surfaces tools and prompts but not general resources, so use `get_workspace_context`, `search_flows`, `search_flow_tools`, and the public docs page there instead of relying on `resources/read`. ## Official Registry And Directory Metadata Breyta Workspace MCP is published as a workspace-scoped remote MCP server named: ```text ai.breyta/workspace-mcp ``` The registry manifest is `server.json` in the Breyta CLI source. It points to the hosted endpoint template: ```text https://flows.breyta.ai/api/workspaces/{workspace_id}/mcp ``` Use the required `workspace_id` variable and an `Authorization` header whose value is `Bearer `. For read-only listings or managed workspaces, add `X-MCP-Readonly: true` and a narrow `X-MCP-Toolsets` value such as `read,setup,debug,feedback`. Directory scanners can read public static metadata at: ```text https://flows.breyta.ai/.well-known/mcp/server-card.json ``` The server card is for discovery and scanning only. It does not replace the workspace-specific MCP endpoint, and it does not grant access to any workspace. Reviewed directories such as Claude and ChatGPT/OpenAI may require an OAuth-compatible auth path, demo workspace, test credentials, privacy review, and complete tool safety annotations before public approval. ## Direct HTTP Or Stdio Proxy Use direct HTTP MCP when the client supports hosted Streamable HTTP MCP servers. Use the Breyta CLI stdio proxy when the client, gateway, or ACP-compatible agent only supports stdio MCP servers. The CLI commands in this section are setup helpers: they create copyable MCP client config, run the stdio proxy when direct HTTP is unavailable, and verify the connection. After setup, the agent can use Breyta MCP tools, resources, and prompts directly for MCP tasks. Both forms are workspace-bound. Do not use a mutable default workspace for MCP server entries; generate one named entry per workspace. Print a direct HTTP config: ```bash breyta mcp config \ --workspace-id ws-acme \ --provider opencode \ --transport http \ --toolsets read,feedback ``` Print a stdio proxy config: ```bash breyta mcp config \ --workspace-id ws-acme \ --provider codex \ --transport stdio \ --read-only \ --toolsets read,setup,debug,feedback ``` `breyta mcp init` is an alias for the same config-rendering setup path. The stdio proxy command used by generated configs is: ```bash breyta mcp stdio \ --workspace-id ws-acme \ --token-env-var BREYTA_MCP_TOKEN \ --read-only \ --toolsets read,setup,feedback ``` Verify setup without invoking mutating tools: ```bash breyta mcp doctor \ --workspace-id ws-acme \ --token-env-var BREYTA_MCP_TOKEN \ --read-only \ --toolsets read,setup,feedback ``` `breyta init --mcp --mcp-workspace-id ws-acme` can print the same workspace-bound config while bootstrapping a local agent workspace. ACP exposure is just MCP server configuration passed into the ACP session; Breyta does not define Breyta-specific ACP custom methods. ## VS Code And GitHub Copilot VS Code stores MCP configuration in `mcp.json`. Workspace config lives at `.vscode/mcp.json`; user config is available from the command palette with `MCP: Open User Configuration`. Use this shape: ```json { "servers": { "breytaWsAcme": { "type": "http", "url": "https://flows.breyta.ai/api/workspaces/ws-acme/mcp", "headers": { "Authorization": "Bearer ${input:breyta_mcp_token}" } } }, "inputs": [ { "type": "promptString", "id": "breyta_mcp_token", "description": "Breyta MCP bearer token", "password": true } ] } ``` Open Copilot Chat in Agent mode, enable the Breyta server in the tools picker, and run the smoke test. ## Codex Codex can add Streamable HTTP MCP servers from the CLI. Store the token in an environment variable before launching Codex: ```bash export BREYTA_MCP_TOKEN="..." codex mcp add breytaWsAcme \ --url https://flows.breyta.ai/api/workspaces/ws-acme/mcp \ --bearer-token-env-var BREYTA_MCP_TOKEN codex mcp list ``` Equivalent `~/.codex/config.toml` shape: ```toml [mcp_servers.breytaWsAcme] url = "https://flows.breyta.ai/api/workspaces/ws-acme/mcp" bearer_token_env_var = "BREYTA_MCP_TOKEN" [mcp_servers.breytaWsAcme.http_headers] "X-MCP-Readonly" = "true" "X-MCP-Toolsets" = "read,setup,debug,feedback" ``` Use the TOML form when you want Codex to enforce Breyta narrowing headers; the `codex mcp add --url` command only covers the URL and bearer token. Add a short project instruction when you want Codex to use the server without being reminded: ```text Use the Breyta MCP server for Breyta flow discovery, setup checks, flow-tool calls, run debugging, and feedback. ``` ## Claude Code And Claude Cowork Sessions Claude Code supports HTTP MCP servers through `claude mcp`. ```bash export BREYTA_MCP_TOKEN="..." claude mcp add --transport http --scope user \ breyta-ws-acme \ https://flows.breyta.ai/api/workspaces/ws-acme/mcp \ --header "Authorization: Bearer $BREYTA_MCP_TOKEN" claude mcp list ``` Use `--scope project` instead of `--scope user` when the server should be shared through a project `.mcp.json` without embedding credentials. In a Claude Code session, run `/mcp` and confirm the Breyta server is connected. For JSON setup: ```bash claude mcp add-json breyta-ws-acme \ '{"type":"http","url":"https://flows.breyta.ai/api/workspaces/ws-acme/mcp","headers":{"Authorization":"Bearer '"$BREYTA_MCP_TOKEN"'"}}' \ --scope user ``` Claude cowork-style environments that read Claude Code MCP config should use the same HTTP endpoint and authorization header. ## Cursor And cursor-agent Cursor reads MCP config from `.cursor/mcp.json` for the project or `~/.cursor/mcp.json` globally. The Cursor CLI uses the same MCP configuration as the editor. ```json { "mcpServers": { "breyta-ws-acme": { "type": "http", "url": "https://flows.breyta.ai/api/workspaces/ws-acme/mcp", "headers": { "Authorization": "Bearer ${env:BREYTA_MCP_TOKEN}" } } } } ``` Verify from the CLI when available: ```bash cursor-agent mcp list cursor-agent mcp list-tools breyta-ws-acme ``` ## Windsurf Windsurf Cascade stores MCP config in `~/.codeium/windsurf/mcp_config.json` and uses `serverUrl` or `url` for remote HTTP MCP servers. ```json { "mcpServers": { "breyta-ws-acme": { "serverUrl": "https://flows.breyta.ai/api/workspaces/ws-acme/mcp", "headers": { "Authorization": "Bearer ${env:BREYTA_MCP_TOKEN}" } } } } ``` Restart or refresh Cascade MCP servers, then run the smoke test. ## Cline Cline CLI config is `~/.cline/mcp.json`. In the IDE extension, open the Cline MCP Servers view, choose Configure, and edit the JSON under `mcpServers`. You can also use the Remote Servers tab with Streamable HTTP. ```json { "mcpServers": { "breyta-ws-acme": { "type": "streamableHttp", "url": "https://flows.breyta.ai/api/workspaces/ws-acme/mcp", "headers": { "Authorization": "Bearer " }, "disabled": false, "autoApprove": [] } } } ``` Keep `autoApprove` empty unless you have narrowed the server with read-only headers and reviewed the exposed tools. ## Roo Code Roo Code supports global MCP settings and project settings. Use project configuration at `.roo/mcp.json` when the server should be tied to one repo; use the global MCP settings UI for personal setup. ```json { "mcpServers": { "breyta-ws-acme": { "type": "streamable-http", "url": "https://flows.breyta.ai/api/workspaces/ws-acme/mcp", "headers": { "Authorization": "Bearer " }, "disabled": false, "autoApprove": [] } } } ``` Prefer manual approval for `call_flow_tool`, `create_flow_draft`, and `release_flow`. ## Continue Continue uses MCP only in Agent mode. If you are editing an existing `.continue/config.yaml`, add the `mcpServers` block. If you are creating a standalone file such as `.continue/mcpServers/breyta.yaml`, include the metadata fields too: ```yaml name: Breyta MCP version: 0.0.1 schema: v1 mcpServers: - name: Breyta workspace ws-acme type: streamable-http url: https://flows.breyta.ai/api/workspaces/ws-acme/mcp requestOptions: headers: Authorization: "Bearer " ``` Continue can also read JSON MCP files copied from Claude, Cursor, or Cline when placed in `.continue/mcpServers/`. ## Gemini CLI Gemini CLI supports MCP server management commands and project or user scope. ```bash export BREYTA_MCP_TOKEN="..." gemini mcp add --transport http --scope user \ breyta-ws-acme \ https://flows.breyta.ai/api/workspaces/ws-acme/mcp \ --header "Authorization: Bearer $BREYTA_MCP_TOKEN" gemini mcp list ``` Equivalent `settings.json` shape: ```json { "mcpServers": { "breyta-ws-acme": { "type": "http", "url": "https://flows.breyta.ai/api/workspaces/ws-acme/mcp", "headers": { "Authorization": "Bearer " } } } } ``` Use `/mcp list` inside Gemini CLI if tools do not appear. ## opencode opencode configures MCP servers under the `mcp` key in `opencode.json` or `opencode.jsonc`. Use `type: "remote"` for the hosted Breyta endpoint. ```json { "$schema": "https://opencode.ai/config.json", "mcp": { "breyta-ws-acme": { "type": "remote", "url": "https://flows.breyta.ai/api/workspaces/ws-acme/mcp", "enabled": true, "oauth": false, "headers": { "Authorization": "Bearer {env:BREYTA_MCP_TOKEN}" } } } } ``` Useful commands: ```bash opencode mcp add opencode mcp list ``` If the server exposes many tools in a large workspace, use opencode tool controls or Breyta `X-MCP-Toolsets` / `X-MCP-Readonly` headers to keep the available context focused. ## Goose Goose treats MCP servers as extensions. Use the interactive setup: ```bash goose configure ``` Then choose: - `Add Extension` - `Remote Extension (Streamable HTTP)` - URL: `https://flows.breyta.ai/api/workspaces/ws-acme/mcp` - Authorization header: `Bearer ` - Timeout: a value appropriate for flow calls, for example `300` Use Goose access controls if you want the agent to ask before running tools that mutate files or call external services. ## Zed Zed uses `context_servers` in its settings rather than `mcpServers`. ```json { "context_servers": { "breyta-ws-acme": { "url": "https://flows.breyta.ai/api/workspaces/ws-acme/mcp", "headers": { "Authorization": "Bearer " } } } } ``` You can also add the server from the Agent Panel settings view with Add Custom Server. ## Optional Narrowing Headers Use these when the MCP host supports custom headers: | Header | Example | Effect | | --- | --- | --- | | `X-MCP-Readonly` | `true` | Hide mutating tools. | | `X-MCP-Toolsets` | `read,setup,debug,feedback` | Include only selected tool groups. | | `X-MCP-Tools` | `search_flows,search_flow_tools,get_run_status` | Include only named tools. | | `X-MCP-Exclude-Tools` | `create_flow_draft,release_flow` | Hide named tools. | Headers narrow the visible tool set. They do not grant permissions; the token must still have the matching Breyta capabilities. Read-only exposure blocks flow runs, draft writes, and releases but still allows `send_feedback` for support sessions when the token has `feedback.send`. ## Troubleshooting | Symptom | Check | | --- | --- | | `401` or `403` | Token value, token workspace, authenticated-principal capabilities, and `Authorization` header spelling. | | Server connects but tools are missing | Run `tools/list` under the same headers, check token/principal capabilities, and remove overly strict `X-MCP-Tools` filters. | | No callable flow tools | The workspace may not have flows with `:interfaces :mcp`, or the authenticated principal may not have `flows.read`. | | Flow tool call is rejected | Read `structuredContent.details.blockers`. Grant missing capabilities to the token/principal, expose the `run` toolset, or remove read-only only when the user approved run access. | | Agent lacks connection context | Add `connections.read`, then use `list_connections` and `check_connections`. | | Agent cannot release a flow | Confirm the token has `flows.manage`, the server either exposes the `release` toolset or does not narrow toolsets, `X-MCP-Readonly` is not enabled, and the user approved the specific flow/version. | | Setup or docs are unclear | Call `send_feedback` with area `setup`, `docs`, or `mcp` and redacted context. | ## Client References - Claude Code MCP docs: `https://code.claude.com/docs/en/mcp` - OpenAI/Codex Docs MCP setup: `https://developers.openai.com/learn/docs-mcp` - Cursor MCP docs: `https://docs.cursor.com/context/model-context-protocol` - VS Code MCP configuration: `https://code.visualstudio.com/docs/copilot/reference/mcp-configuration` - Windsurf Cascade MCP docs: `https://docs.windsurf.com/windsurf/cascade/mcp` - Cline MCP docs: `https://docs.cline.bot/mcp/configuring-mcp-servers` - Continue MCP docs: `https://docs.continue.dev/customize/deep-dives/mcp` - Gemini CLI MCP docs: `https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md` - opencode MCP docs: `https://opencode.ai/docs/mcp-servers` - Goose extension docs: `https://goose-docs.ai/docs/getting-started/using-extensions/` - Zed MCP docs: `https://zed.dev/docs/ai/mcp` --- 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-26T12:18:20+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 uses one child-workflow mode: `{:type :call-flow ...}` or `{:type :agent ...}` ## 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-06-01T14:57: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 ``` There is no `breyta runs output ` command. Use the run, step, and resource inspection commands directly: ```bash breyta runs show --pretty breyta runs step --full --pretty breyta resources workflow list breyta resources read --limit 20 ``` 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: Marketplace App Pricing URL: https://flows.breyta.ai/docs/guide-marketplace-app-pricing.md HTML: https://flows.breyta.ai/docs/guide-marketplace-app-pricing Last updated: 2026-05-29T09:48:02+02:00 # Marketplace App Pricing ## Quick Answer For paid Breyta marketplace apps, start with subscriptions or prepaid usage packs. Avoid tiny payments and per-run microtransactions. Breyta apps can have very little operational overhead, so the price floor can be low. The recommended price should still reflect buyer value, payment fees, Breyta platform fees, run costs, external API or LLM costs, and support burden. ## Choose A Pricing Model | Use case | Recommended model | | --- | --- | | Recurring outcome, such as weekly reports or ongoing monitoring | Subscription | | Recurring app with included successful runs each month | Subscription usage | | Repeat use with uneven timing | Prepaid usage pack | | Fixed utility with enough value to justify checkout | One-time purchase | | Demo, lead magnet, or sampling | Free app or trial runs | Usage pricing in Breyta means prepaid run packs. The buyer pays once for a fixed number of successful completed runs. Subscription usage includes a fixed number of successful runs per billing period. It does not bill paid overages today; when included runs are exhausted, the buyer is blocked until renewal. ## Estimate The Cost Floor Use run cost estimates before finalizing app pricing: - `Avg Estimated Run Cost` on the flow page - `data.run.costEstimate` from `breyta runs show` - step-level `:metering` for fixed API, enrichment, or tool request costs - LLM token estimates when model pricing is known - external provider costs paid outside Breyta - expected creator support burden Run cost estimates are advisory. They help creators understand margins, but they are not the buyer billing ledger. ## Pricing Rules Of Thumb - Price from buyer value first, then verify margin. - Use prepaid packs or subscriptions to avoid fixed-fee drag from tiny charges. - If per-run cost is unknown, start with a smaller trial or included-run limit. - If support burden is high, prefer subscription pricing. - If usage is predictable, use subscription usage. - If usage is repeatable but uneven, use prepaid packs. - If the app is mostly a demo, offer trial runs instead of a tiny paid checkout. ## Good Starting Shapes Use these as starting points, then adjust for buyer value, costs, and support: | App pattern | Better starting shape | | --- | --- | | Weekly report, monitor, or alert | `$49/mo` with 100 included runs and five trial runs | | High-value AI review or enrichment | `$99/mo` with 50 included runs and a smaller trial cap | | Irregular batch utility | `$99` prepaid pack with 500 successful runs | | Fixed diagnostic or setup utility | `$199` one-time purchase when the result is valuable enough to justify checkout | | Demo or lead magnet | Free access or five trial runs | If the first instinct is `$1/run`, bundle it. Use a `$49` or `$99` prepaid pack, a subscription, or a trial instead. ## Margin Worksheet Use this quick worksheet before recommending a price: 1. Start with the buyer price. 2. Subtract Breyta platform fees and payment fees. 3. Subtract expected run costs at full included usage. 4. Subtract external API, search, enrichment, or LLM costs. 5. Subtract a support allowance when the creator expects setup help or manual follow-up. 6. Check whether the remaining margin is worth selling and supporting. Example: a `$99` prepaid pack with 500 runs gives the creator `$0.198/run` before fees. If estimated run and external costs are `$0.04/run`, full usage costs about `$20`. The remaining `$79` before fees and support is the margin pool. If that is too thin, reduce included runs, raise the pack price, or move to a subscription. ## Related - [Paid Public Flows](/docs/guide-paid-public-flows) - [Run Cost Estimates](/docs/guide-run-cost-estimates) - [Flow Metadata And Discover](/docs/reference-flow-metadata) --- 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-28T13:30:01+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", "stepId": "draft-review", "stepType": "llm", "provider": "openai", "model": "gpt-5-mini", "requestedModel": "gpt-5.5", "actualModel": "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. - LLM line items include `requestedModel` and `actualModel` when the provider reports enough data. Use these fields to audit defaulting, fallbacks, or provider-side model changes. - 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 - [Marketplace App Pricing](/docs/guide-marketplace-app-pricing) - [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: Marketing Hub URL: https://flows.breyta.ai/docs/guide-marketing-hub.md HTML: https://flows.breyta.ai/docs/guide-marketing-hub Last updated: 2026-08-04T12:02:33+02:00 # Marketing Hub Use Marketing Hub to keep the marketing work you care about in one workspace view. Ask the Breyta agent to add tabs and cards from your sources, Storage files, flow outputs, and suggested next actions. ## What You Can Ask For Examples: - "Build a Home tab with this week's runs, success rate, and next actions." - "Add an SEO tab with a rank table and the latest content brief." - "Show the latest campaign output without making me open the run." - "Add a campaign gallery from these Storage results." - "Put our current initiative and Friday checkpoint at the top." - "Remove this card and undo the last dashboard change." The Hub supports: - metrics - tables and charts - artifacts and latest successful outputs - Markdown notes and narratives - marketing suggestions that you can approve, discuss, or dismiss ## How Changes Work The workspace agent is the dashboard editor. 1. Tell the agent what outcome you want. 2. The agent reads the current dashboard and the available card catalog. 3. It preserves unrelated tabs and cards. 4. It applies your requested change. 5. The Hub refreshes while the persistent chat remains available. Direct requests can be applied immediately. If the agent has an idea you did not ask for, it should explain the idea and wait for your confirmation before changing the dashboard. ## First Setup An empty workspace does not create a dashboard automatically. After the workspace has a useful source, run, or marketing suggestion, Breyta can create the first Home tab through an authenticated, idempotent browser action. An explicit UI Chat or CLI request can create the first dashboard sooner with revision `0`. Page reads and live update connections never create or repair dashboard state. ## Tabs And Live Data Only the selected tab loads its connected data. This keeps dashboard work bounded even when the Hub has several tabs. For latest-output cards: - Breyta uses the exact flow and run profile selected for the card. - The last successful result stays visible if a newer run fails. - The card shows that the result is stale without exposing provider errors or internal run details. Connected run, flow catalog, dashboard, and supported Breyta file, resource, and table changes refresh live. If a source changes outside Breyta's supported mutation paths, use **Refresh** to reread it. ## Email A Tab On a populated tab, open **Share**: - **Create email update** prepares a one-off report. - **Schedule weekly email** prepares a recurring report and schedule. Both options place a detailed request in UI Chat for you to review. Nothing is sent, submitted, or scheduled when you click the menu item. The agent checks your email connection, builds the report from the tab's underlying sources and outputs, and shows the exact email before sending. A weekly report rereads those sources each time and stays disabled until you approve the recipients, sender, subject, HTML, day, time, timezone, and failure behavior. Email updates can include metrics, HTML tables, and safe links to reports or outputs. Graphs remain available in the linked report or Hub in this first version; they are not attached as chart images. Workspace links may require sign-in. External emails contain no links unless you explicitly approve a public share and its expiry. ## Collaboration And History The dashboard is shared between the workspace creators and admins who use the Hub. - Creator and admin members can change, undo, and restore it. - Existing role-based home routing is unchanged. Marketing Hub does not add Overview access for buyer roles. - Suggestion cards are resolved for the person viewing the Hub. History keeps the latest 50 dashboard revisions. Restoring an earlier layout creates a new revision, so newer history is not deleted. ## Safety The agent chooses from a fixed component and action catalog. A dashboard cannot store or execute arbitrary JavaScript, browser handlers, React components, raw web URLs, or viewer-specific suggestion ids. Storage and output cards still use normal workspace authorization. A resource from another workspace cannot be attached by changing the dashboard configuration. ## Agent Command Reference The authenticated workspace-agent CLI surface is: - `breyta agent dashboard show --pretty` - `breyta agent dashboard catalog --pretty` - `breyta agent dashboard apply --expected-revision --manifest-file --change-summary ""` - `breyta agent dashboard history --limit 20 --pretty` - `breyta agent dashboard restore --expected-revision --revision --change-summary ""` Dashboard writes use the latest `expectedRevision`. When `show` reports `bootstrapRequired: true`, revision `0` creates the first dashboard directly from the terminal. It fails with a revision conflict if another collaborator creates the Hub first. For every later write, the agent must reread, merge, and retry rather than overwrite newer work. ## Related - [Runs And Outputs](/docs/operate-runs-and-outputs) - [Persisted Results And Resources](/docs/guide-persisted-results-and-resources) - [Output Artifacts](/docs/guide-output-artifacts) --- 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-06-02T13:06:57+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 :initial-interval-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-07-30T18:15:38+02:00 # CLI Essentials Breyta CLI essentials for the default draft/live workflow. This page is for terminal, CI, local operator, and agent workflows that use the CLI. If you are connecting an agent through MCP, see [Workspace MCP](/docs/mcp-workspace) and [Connect Breyta MCP To Agentic Coding Tools](/docs/mcp-client-setup). Both the CLI and MCP are valid agent paths; use the surface that matches the host and task. ## Quick Answer Use this default path for a new flow: `breyta flows init ` -> edit with `flows steps ...` / `flows compose` -> `breyta flows lint --file ./flows/.clj` -> optional `breyta flows steps run --params ''` -> `breyta flows push --file ./flows/.clj` -> `breyta flows configure --set .conn=conn-...` (if required) -> `breyta flows configure check ` -> `breyta flows run --wait` For an existing remote flow, start with `breyta flows pull --out ./tmp/flows/.clj`. Local edits do not write a draft until `flows push` (or an explicit command-level `--push`) is used. ## 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 init --name "My flow" [--input 'site-url:text:required:Site URL']` | Create the canonical local source and empty `:schedules`. Repeat `--input name:type[:required\|optional[:label]]` to seed manual invocation inputs. Local only unless `--push` is explicit. | | `breyta flows steps create --step-file ./step.edn` | Add one packaged step to the local top-level `:steps` vector. | | `breyta flows steps update/remove ...` | Replace or remove one local packaged step without rewriting the orchestration body. Removal is rejected while the local `:flow` still references that qualified step. | | `breyta flows schedules add --cron "0 9 * * MON" --timezone UTC` | Add one schedule to the local top-level `:schedules` vector. | | `breyta flows schedules update/remove ...` | Replace a complete schedule map or remove one local schedule without rewriting the flow body. | | `breyta flows compose --body-file ./body.clj` | Replace only the quoted top-level `:flow` form. | | `breyta flows steps run --params ''` | Execute a packaged step from the complete local literal without persisting a draft. `--timeout` defaults to 15 minutes and is also available on the `--run` modes of `flows init` and `flows steps create/update`. | | `breyta flows run-step --target draft --input '' --wait` | Canonical authoring-time probe for a named inline function/code step or an LLM step using draft-bound connection slots. Pass the flow invocation's root input object. `flows steps run` is only for qualified top-level packaged `:steps`; `steps run --flow` is a lower-level primitive/config probe. | | `breyta flows lint --file ./tmp/flows/.clj` | Fast local-first candidate source checks before mutating draft, including missing qualified packaged-step references. Add `--server` for canonical non-mutating API lint; server lint is bounded by `--timeout` (default 30s). | | `breyta flows push --file ./tmp/flows/.clj [--timeout 2m]` | Persist and validate the workspace draft. The timeout defaults to two minutes per API request and applies separately to saving and validation, so the whole command can run longer than two minutes. After a timeout, inspect with `breyta flows show ` or `breyta flows validate ` before retrying because the draft may already be saved. | | `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 connection slots, secret slots, and activation 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. For manual file/blob-ref inputs, use `--upload field=path`. | | `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, an ordinary `--set .secret=` stores `` as new plaintext under the declared `:secret-ref`, and `--set activation.=` saves a setup input. The documented `:generate` sentinel requests server-side generation. Never pass a secret ID as `` to reuse it. Omit the secret field only to preserve an already-bound target value; omission does not bind an unconfigured draft target. Use `flows doctor` or `flows readiness` when checking target readiness, then run a provider-specific, read-only smoke test to verify the credential itself. 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-25T08:25:09+02:00 # CLI Agent Onboarding Agent-first onboarding for Breyta when the agent can run local CLI commands. For agents connected through MCP, see [Connect Breyta MCP To Agentic Coding Tools](/docs/mcp-client-setup). This CLI onboarding page remains the right path when the agent can run local commands, needs local project instructions, or is working through terminal, CI, and operator workflows. Both surfaces are supported; choose based on the host and task. ## 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-06-02T13:06:57+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 :initial-interval-ms 500}}) risk (flow/step :http :fetch-risk {:connection :orders-api :template :fetch-risk :data {:order-id order-id} :retry {:max-attempts 3 :initial-interval-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 :initial-interval-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-07-28T07:40:02+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. `flow/step` takes exactly three arguments: step type or packaged id, step id, and one config map. Outer controls such as `:on-error`, `:retry`, `:timeout`, and `:persist` are keys inside that config map, never a fourth argument. 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. ## Public App Intent Gate When the requested outcome is a public app (installable, Discover, or marketplace), design the installer's setup screen before any flow source: - target at most one connection card per provider account and origin plus at most one required text field; reject any `:requires` list where two slots share a provider account and origin - public unauthenticated endpoints need no connection slot; `:http` with a plain `:url` works for public GETs such as sitemaps and feeds - declare one connection per provider account and origin; prefer a bare-origin `:base-url` with path prefixes in step `:path`, and never split slots by path prefix on the same origin. Distinct API hosts need separate slots; keep provider-required prefixes, such as a self-managed GitLab `/api/v4` `:base-url`, when a provider-backed step's contract demands them - derive the flow from the approved setup screen, not the setup screen from the flow - load `references/public-flows.md` (setup screen, degradation contract) and `playbooks/public-and-marketplace.md` before writing source Workspace-internal flows keep the normal loop below. This gate applies only when public-app intent is stated or becomes apparent. ## 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. For a new flow, run `breyta flows init --name "..."`. It creates the local canonical `.clj` source with one no-input manual `run` interface and an empty `:schedules` vector. For an existing flow, use `breyta flows pull`. 5. Build a minimal local slice with one meaningful boundary. Add packaged step definitions with `breyta flows steps create/update/remove`, add schedules with `breyta flows schedules add/update/remove`, and compose the orchestration separately with `breyta flows compose`; these helpers preserve the other top-level definition surfaces. 6. Run fast feedback before mutating draft: - `breyta flows lint --file ./flows/.clj --local-only` for offline static checks: missing qualified packaged-step references, `flow/step` arity and shape errors that mirror push validation, and unreferenced packaged-step warnings (plain-literal forms only; reader-macro or runtime-computed forms are skipped by design) - `breyta flows lint --file ./flows/.clj --server` when API context is available and canonical pre-push checks matter; add `--timeout ` for unusually slow server checks - `breyta flows steps run --params ''` to execute a packaged step from the complete local literal without persisting a draft - `breyta flows push --file ./flows/.clj [--timeout 2m]`; the timeout defaults to two minutes for each of the save and validation requests, and if either request times out, verify the draft with `breyta flows show ` or `breyta flows validate ` before retrying - `breyta flows configure check ` when the flow has required slots - `breyta flows validate ` after push for stored draft/live validation 7. 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 Treat steps 5-7 as one loop per step, not one pass per flow: add one step, run that step, read its real output, then write the next step; for orchestration-only steps that `steps run` rejects, use a minimal flow run. Composite debugging after five unproven steps hides which boundary lied. ## Child Flow Targets An author-owned child flow that declares required connections, secrets, or setup values needs a profile target. During a draft run, an unqualified child call automatically selects the enabled draft profile for the child flow in the same workspace: ```clojure (let [input (flow/input)] (flow/call-flow :review-social-draft input)) ``` The parent draft's inherited draft-version marker triggers this lookup; the child's profile supplies the child flow's own bindings and setup values. The source remains portable between workspaces and does not hardcode a profile id. If the child has no enabled draft profile, pass an explicit child `:profile-id` or configure the draft profile before running the parent. For installed public children, use the installation id in the third options map instead: ```clojure (flow/call-flow :reviewed-ai-response-generator {:prompt prompt} {:installation-id reviewed-ai-response-installation-id}) ``` When the parent itself is executing inside a linked public installation, an unqualified same-app child call inherits that linked context automatically. ## 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 Single-step runs are the required cadence for supported activity-backed steps, not an optional optimization: build one step, run it, inspect the real output, then write the next step. `steps run` rejects `:sleep`, `:wait`, and `:fanout`, and control-plane steps only execute inside a flow run, so prove orchestration-only steps with a minimal full-flow run instead. Use single-step commands instead of full workflow reruns while developing the 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 `--profile-id ` when slot-backed connections should resolve from a saved draft/live profile; use `--installation-id ` for installed-run proof. The command waits up to five minutes by default; use `--timeout 10m` for a slower provider call. For an LLM prompt stored in the selected flow's `:templates`, pass the template ID and its variables separately. `:template` selects a flow-local template; `:data` supplies the values used by `{{...}}` expressions: ```bash breyta steps run --flow update-blog-post --source draft \ --profile-id --type llm --id refresh-blog-post \ --params-file ./tmp/refresh-blog-post.params.json --timeout 5m ``` ```json {"template":"refresh-blog-post","connection":"ai","data":{"title":"Example","article":"..."}} ``` The `--flow` and source flags are what let the server resolve the flow-local template. Replace `ai` with the connection slot declared by the flow. A catalog/template slug is not a substitute for the flow's local template ID. If the request still reaches the CLI deadline, increase `--timeout`; this changes the client request bound, not the provider's own model or upstream timeout. 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. Normalization is recursive over nested maps and applies to packaged-step input params too, so author kebab-case keys in every `:input-schema` and cross-step contract; a camelCase schema key never matches its normalized runtime key. `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` (exactly one argument, no parser flags) and `json/write-str` for JSON - use `breyta.sandbox/*` helpers for common safe utilities - for time and dates, use allowlisted `java.time.*` interop plus `breyta.sandbox` instant helpers such as `parse-instant` and `instant->epoch-ms`; `System/currentTimeMillis` is blocked. The flow body has no Java interop; its deterministic time source is `(flow/now-ms)` - avoid large custom parser/guard layers unless external input is truly ambiguous - coalesce optional inputs with a first-present reduce over candidate keys, not `or`; only `nil` and `false` are falsey in Clojure, so `or` treats an explicit `false` as absent and silently disables opt-outs. Prove both cases with a step run: absent input takes the default, explicit `false` stays `false` - after any CLI/JSON round-trip, scan the source for doubled backslashes in regex literals; `#"\\s+"` matches a literal backslash plus `s`, not whitespace. Probe every decode and regex helper before shipping — packaged steps with `breyta flows steps run`, inline or `:functions` helpers with `breyta steps run --flow --source draft --type function --id `; these bugs fail silently ## Packaged Step Shape Top-level `:steps` entries wrap one built-in step: - shape: `{:id :ns/name :type :description "..." :input-schema [:map ...] :defaults {...}}` plus optional `:output-schema`, `:prepare`, `:project-result`, and `:tool` - `:id` must be a qualified keyword; `:config` is not a wrapper key, and `:description` plus `:input-schema` are required - the `flow/step` call's input map merges with `:defaults` into the wrapped step config; outer controls ride in that same input map - declare kebab-case `:input-schema` keys so they survive JSON param normalization - a step that fails under `:on-error :continue` binds `nil`; treat `nil` as the failure signal, and check response fields such as `(:status result)` only when the wrapped step's output contract declares them ## 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](/docs/reference-output-artifacts) - [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-07-27T20:51:24+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`. - Do not use `breyta runs output `. There is no `runs output` command; inspect run data, step payloads, or resource refs instead. - `breyta runs show --pretty` returns run detail and output URLs when available. - `breyta runs show --include-result` is the canonical one-command read of a run's final output: the full result in a bounded envelope with no step captures. Add `--installation-id ` for installation-scoped runs. Prefer it over `runs inspect --full`, which also dumps every step capture. - `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: `breyta runs step --full --pretty`. - `breyta resources workflow list ` lists persisted artifacts; then read the relevant ref with `breyta resources read --limit 20`. 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](/docs/reference-output-artifacts) - [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-07-27T11:46:30+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. ## Install Test Matrix For installable/public flows, prove install-shaped behavior through `breyta flows installations create/configure/enable` and `breyta flows run --installation-id --wait`, not author draft runs. A fresh installation needs an installable source, so run the matrix right after release/publish and before announcing the app or calling it end-user ready. For a paid app, use Buyer Test Mode so the matrix consumes no real buyer entitlement: `breyta flows installations create --buyer-test-source-install --source-workspace-id --source-flow-slug ` from the Buyer Test workspace, then `breyta flows run --buyer-test --installation-id --wait`. Cover the surfaces the flow declares: - a fresh installation with minimal configuration completing a run - each declared optional integration absent, and separately bound but broken; the run must complete and the report must say what was skipped and why - each declared boolean toggle passed as explicit `false`, proving the opt-out holds - malformed values for each constrained text input, proving validation shows a truncated attempted-value preview and a bounded option sample; genuinely free-form text needs a format or limit error, not an options list Runs consume the buyer workspace's paid or trial allowance only when they complete; failed, cancelled, and timed-out runs release their reservation. Still budget matrix runs deliberately and fix configuration between attempts; report allowance surprises with `breyta feedback send`. ## Stop When - live target configuration is valid - representative live/install run succeeded - for installable/public flows, the install test matrix passed through installation-scoped runs - 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](/docs/reference-output-artifacts) --- 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-07-27T14:37:33+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 discover search "" --include-own --limit 5` when debugging own public indexing - `breyta flows discover show /` to inspect any public app's full listing (publish copy, pricing, connections, versions), including apps owned by other workspaces. `breyta flows show` only works inside workspaces you are a member of. 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 - complete the install test matrix in `playbooks/release-and-install.md` once the app is installable (right after release/publish, before announcing it): minimal config, plus per declared surface: optional integrations absent and broken, explicit-false toggles, malformed constrained text - 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. - Use `breyta flows public publish ` or `breyta flows public delist ` when moving a flow onto or off all public surfaces together. - Use `breyta flows marketplace update` or `breyta flows discover update` only when intentionally changing one lower-level flag. 6. Inspect public output from the intended audience perspective. Markdown, tables, media, and resource viewers should be readable without raw debug payloads. 7. For paid pricing work, load `references/public-app-pricing.md`. Prefer prepaid usage packs and subscriptions; avoid tiny payments and per-run microtransactions. 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 marketplace publish description is stored flow metadata: author it in source as `:publish-description` and persist it with `breyta flows push`, or set it directly with `breyta flows update --publish-description-file ...` for metadata-only edits. Update and release both republish changed metadata for already-public flows; reserve `breyta flows public publish ` for visibility changes or an explicit retry after a failed publish. Prove propagation by fetching `https://breyta.ai/apps/` with a bot user agent and checking the updated copy appears; the CMS publisher run has no author-facing status surface. - Optional-integration failures that kill installed runs, silent-zero report lines, and `or`-coalesced booleans that swallow explicit `false` are the most common post-launch defects; design against the degradation contract in `references/public-flows.md` before publish. - Installed-app manual-run QA can be invalidated by flow concurrency. If you expect multiple in-flight runs from the installed app page, do not leave the flow on `:concurrency {:type :singleton :on-new-version :supersede}`. That policy intentionally replaces the earlier run, which looks like "only the last run exists". Prefer `:on-new-version :coexist` unless replacement is the product behavior you actually want to ship. - 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. - Paid app pricing should usually use subscriptions, subscription usage, or prepaid run packs. Use free or trial runs for sampling instead of tiny paid checkout. ## References - [Paid Public Flows](/docs/guide-paid-public-flows) - [Marketplace App Pricing](/docs/breyta-cli-skill-public-app-pricing) - [Installations](/docs/guide-installations) - [Flow Metadata](/docs/reference-flow-metadata) - [Output Artifacts](/docs/reference-output-artifacts) --- 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-28T13:49:15+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. Async `:fanout` supports one child-workflow mode at a time: child-flow items (`:call-flow`) or named agent items (`:agent`). Use grouped flow metadata for workspace display, but runtime linkage comes from `flow/call-flow`, `:fanout`, or another orchestration primitive. For the common multi-agent review shape where a coordinator decides specialist work dynamically, prefer the agent-internal fanout tool and see [Orchestration Agent Fanout](/docs/guide-orchestration-agent-fanout). ## 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) - [Orchestration Agent Fanout](/docs/guide-orchestration-agent-fanout) - [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-07-17T16:15:19+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, and one webhook adapter. MCP interfaces may list multiple tools when one flow deliberately offers several agent-callable operations, but each tool should map to the invocation contract it actually uses. 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 an authored Breyta flow reuses an installed public flow internally, use `flow/call-flow` with `:installation-id` so the child run keeps native workflow lineage and the installer's setup, billing entitlement, and ownership context. Coding agents and external clients should use the installed HTTP or MCP interface instead of author draft/live endpoints. 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. MCP interface descriptions and invocation input annotations are hidden from model-facing tool metadata by default. They remain useful for humans, search, and setup UI. 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 '(let [issues (flow/step :mcp :fetch-issues {:adapter :linear/issues :tool "list_issues" :arguments {:team_id "ENG"}})] issues)} ``` Use `flow/step :mcp` when orchestration should deterministically call one named remote MCP tool. The step resolves the top-level adapter, applies adapter `:allow-tools`/`:deny-tools`, and sends JSON-RPC `tools/call` over the existing HTTP activity path. Use `:tools {:mcp [...]}` on `:llm` or `:agent` when a model or agent should choose if and when to call MCP tools. 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. Agentic MCP calls run through the same tool loop as built-in, packaged-step, and agent-to-agent tools. For direct step fields and result shape, see [Step MCP](/docs/reference-step-mcp). 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"}]} ``` For local source authoring, use the CLI helpers so schedules stay in the same flow literal as interfaces, invocations, and packaged steps: ```bash breyta flows schedules add \ --cron "0 9 * * MON" --timezone UTC breyta flows schedules update \ --schedule-file ./schedules/.edn breyta flows schedules remove ``` These commands edit the local top-level `:schedules` vector and do not write remote state unless `--push` is explicit. The add shortcut defaults the label to the schedule id, invokes `:default`, and enables the authored schedule; use `--enabled=false` or a complete `--schedule-file` when the authored default should start disabled. `flows configure` remains the remote author/install schedule-settings surface. 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 or flows visible in the installer workspace. | | `: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 or flows visible in the installer workspace. | | `: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. - Supported source types: - `:connection-items`, which reads non-secret cached item metadata for the selected connection binding. - `:workspace-flows`, which lists editable flows in the installer workspace. - 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 dynamic options cannot be resolved or the submitted value is not currently present in the resolved options. For connection item sources this also covers missing backing connections, empty caches, and disabled items. - 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. - For `:connection-items`, 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. - For `:connection-items`, `: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"`. - For `:connection-items`, `: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 source payload fields into dropdown values. For `:connection-items`, omitted fields fall back to `value`/`id` for the submitted value, `label` for display text, and `description` for helper text. For `:workspace-flows`, omitted fields fall back to flow slug for the submitted value, flow name for display text, and flow 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"}}]}}} ``` Workspace flow picker example: ```clojure {:requires [{:kind :form :collect :setup :fields [{:key :target-flow :label "Flow to update" :field-type :select :required true :options-source {:type :workspace-flows}}]}]} ``` 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 | | `:json` | Structured JSON input for object/array payloads, especially API/MCP entrypoints | | `: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. For draft verification, `breyta flows run --target draft --input '{}' --wait` also supports the older manual-run shape. If the draft has exactly one enabled legacy manual trigger, it is selected automatically even when that older source omitted the trigger `:id`; the run input is still checked against that trigger's fields. If it has multiple manual triggers, pass `--trigger-id ` (or migrate to a declared manual interface and use `--interface-id `) so the server does not have to guess. When an explicit `:invocations` map is present, select its contract with `--invocation `; an empty map intentionally suppresses legacy run-field derivation. ## 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. For multi-agent orchestration where one coordinator agent chooses sibling specialists during its own reasoning, prefer the agent-internal fanout tool and see [Orchestration Agent Fanout](/docs/guide-orchestration-agent-fanout). `: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 ...}` or `{:type :agent ...}` 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 deterministically needs several sibling child workflows, keep the batch bounded and use `:fanout` with either all `:call-flow` items or all named `:agent` items. Legacy non-child fanout remains available for compatibility, but it runs on the sequential compatibility path. If a coordinator agent decides which sibling specialists to run dynamically, use the agent-internal fanout tool described in the [Orchestration Agent Fanout](/docs/guide-orchestration-agent-fanout) guide for the source shape and guardrails. ## 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 [...]}` - coordinator agents can publish a bounded parallel specialist tool via `:tools {:fanout {: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. For sibling specialists that should work in parallel, prefer `:tools {:fanout ...}` when the coordinator agent should choose the branches. Use `flow/step :fanout` with `:agent` items when `:flow` already knows the deterministic branch list: ```clojure '(let [input (flow/input) reviews (flow/step :fanout :parallel-reviews {:items [{:type :agent :agent :review/security :input {:area "auth" :repo-tree (:repo-tree input)}} {:type :agent :agent :review/performance :input {:area "queries" :repo-tree (:repo-tree input)}}] :max-concurrency 2 :on-error :collect})] reviews) ``` For the full orchestration-agent version of this pattern, see [Orchestration Agent Fanout](/docs/guide-orchestration-agent-fanout). ## 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-07-27T14:15:57+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` | ## Ephemeral Local-Literal Step Runs `breyta flows steps run ` sends the complete local source as `args.flowLiteral` to `steps.run`. The API validates that literal in memory, resolves the qualified step id from the top-level `:steps` vector, and executes it through the normal runtime without creating or updating a flow draft. The response identifies this explicitly: ```json { "data": { "stepType": "function", "stepId": "tools/add-one", "flow": { "flowSource": "literal", "persisted": false, "sourceHash": "sha256..." }, "result": {"answer": 3} } } ``` This is a proof surface, not a second manifest. The local `.clj` file remains the source of truth; use `breyta flows push --file ...` when the source should be persisted as the workspace draft. ## 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 :function :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 Run and step preview surfaces redact secret-like map values before returning them to CLI/API/UI clients. Keys such as `authorization`, `developer-token`, `api-key`, `token`, `password`, and `secret` render as `[REDACTED]` in `resultPreview`, run events, run step output, and run inspection 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. For `application/json` resources, `:load [:field]` hydrates parsed data. When the next API needs the original file contents, such as a GitHub Contents update for `package-lock.json`, use `:load {:field :text}` or `{:field :raw}` to receive the raw UTF-8 string instead. For binary files (docx, xlsx, pptx, pdf, images) use `:load {:field :bytes}`; `:text`/`:raw` UTF-8-decode and corrupt them. Pair with `breyta.sandbox/base64-encode` for APIs that take base64 or `data:` URIs. 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](/docs/reference-output-artifacts) - [Table Resources](/docs/reference-table-resources) --- 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-07-24T11:25:19+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`. ## Installed App Connection Setup For a public app installation, configure installer-owned connections from the installation setup page. Do not copy or reuse the app author's connections. Start by inspecting the installation: ```bash breyta flows installations show --pretty ``` The response makes the setup contract explicit: - `activationInputKeys` lists the exact keys accepted by `breyta flows installations configure --input`. - `activationInputs` contains only current installer-editable values. - `bindingKeys` lists the exact slots accepted by `configure --bindings`. - `bindings` contains only current installer-owned bindings. - `requires` describes the installer-owned connection slots and provider shape. - `setupUrl` opens the installation's setup page. Then: 1. Open `setupUrl`. 2. For each installer-owned connection in `requires`, reuse a compatible connection from the installer workspace or authorize a new one through the provider card. 3. Select any provider item requested by setup, then save. 4. Run `installations show` again and confirm that required bindings are present and `runReadiness.ready` is `true`. Optional keys in `bindingKeys` may remain unbound. A suitable existing connection can also be bound directly. For an ordinary connection slot: ```bash breyta flows installations configure \ --bindings '{"":""}' \ --input '{"":""}' ``` For a `blob-storage` slot, include its storage root: ```bash breyta flows installations configure \ --bindings '{"":{"connectionId":"","config":{"root":""}}}' ``` Use the exact names from `bindingKeys` and `activationInputKeys`. Connection slots and setup inputs are declared by each installed app; they are not global aliases. ## 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. 4. Choose `Save for installers` when the connection is only an author-owned template for installers, or `Save and connect` when you also want to authorize your own provider account for testing. The setup shape is: 1. Create a pending `OAuth account` connection/template. 2. Fill in the provider label, base URL, authorize URL, token URL, probe URL, scopes, and client credentials. 3. For author test connections, complete the OAuth browser redirect. 4. For installer-owned public flow slots, save the installer template, reference that template connection ID from the slot, release a new flow version, and let installers authorize their own account from setup. CLI starter: ```bash breyta connections create --type oauth-account --name "LinkedIn OAuth" --base-url https://api.linkedin.com --slot linkedin ``` This creates a pending reusable OAuth account connection. The create type is a setup alias; the stored connection remains an `http-api` connection with custom OAuth defaults. Complete the provider fields and browser OAuth redirect in the connection UI before testing or binding it. ### 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"}`. For custom providers that need account switching, use the provider's documented prompt parameter, such as `{"prompt":"select_account"}`. | | 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. - Built-in Google and Microsoft reconnects can request the provider account chooser automatically. Custom OAuth account switching is provider-specific, so configure the required authorize param in the template when the provider documents one. 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-07-30T18:15:38+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 for recent-run row titles Run form titles are independent from final output viewer metadata and persisted resource names. 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. Authors can create the template without authorizing their own provider account by choosing `Save for installers`; `Save and connect` remains available when the author also wants a local test connection. 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 - add provider-specific account chooser prompts to the template's extra authorize params when the provider requires them for switching accounts - 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 - Extra authorize params: any provider-required account chooser or consent params - PKCE: enabled Choose `Save for installers` unless the author also wants to connect a test account. ``` 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 | Configure check responses include separate readiness fields for connection slots, secret slots, and activation inputs. Required secret slots appear under `requiredSecretSlots` and keep `ready` false while listed in `missingSecretSlots`. | 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 - except for documented control sentinels such as `:generate`, treat `.secret=` as an intentional plaintext create or replacement under the slot's `:secret-ref` - never pass a secret ID or `:secret-ref` name as `` to reuse it; omit the secret field only to preserve an already-bound target value, since omission does not bind an unconfigured draft target - 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 installable apps, keep shared platform credentials `:provided-by :author` and customer account connections `:provided-by :installer` - treat configure readiness as proof that binding or reference metadata is present, not proof that the referenced secret still exists or is valid; run a provider-specific, read-only smoke test to prove that it works 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)). ### How do I preserve an already-bound secret without replacing it? Keep the slot's existing `:secret-ref` and omit `.secret` from the configuration request. This preserves an existing target binding; it does not bind an unconfigured draft target even if the workspace secret store already contains that ref. An ordinary `.secret` value is stored as new plaintext and is not interpreted as a secret reference. The documented `:generate` sentinel requests server-side generation instead. ### How do author-provided secrets work in an installable app? Configure the secret once on the author's live target and declare the requirement `:provided-by :author`. Installers do not see that field. Declare their own account or OAuth connection `:provided-by :installer`. ## Common Pitfalls - running before required config is applied - using configuration commands after a failed push - mixing workspace-target config with installation-target config - passing a secret ID through `.secret` and replacing the stored value - 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-07-20T11:26:06+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. ## Child Flow Calls Use the positional `flow/call-flow` form: ```clojure (flow/call-flow :child-flow {:plain "child input"}) ``` For installed public child flows, pass the installation in the third options map: ```clojure (flow/call-flow :reviewed-ai-response-generator {:prompt prompt} {:installation-id reviewed-ai-response-installation-id}) ``` For an author-owned child flow in the same workspace, a draft run may need a child profile when the child uses profile-bound connections, secrets, or setup values. An unqualified call from a draft parent automatically selects the child flow's enabled draft profile: ```clojure (let [input (flow/input)] (flow/call-flow :review-social-draft input)) ``` The inherited draft marker is resolved separately for each child flow, so this does not reuse or expose the parent's profile id. If the child has no enabled draft profile, pass an explicit child `:profile-id` after configuring it. For an installed public child flow, use `:installation-id`; a workspace draft profile is not the consumer's installation. When a parent is already running inside a linked public app, omit the target and let the linked install context flow to same-app children. Do not put `:installation-id` in the child input map, and do not use a one-map shape such as `(flow/call-flow {:flow-slug ... :installation-id ... :input ...})`. ## 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) ## 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-07-21T14:29:09+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`. `flow/poll` bounds Breyta's status-check loop. It does not cancel the remote actor, cap the actor's emitted events, or enforce provider billing. Provider inputs such as `maxPosts`, `limit`, or `maxItems` are request parameters whose semantics vary by provider; do not treat them as a Breyta-side cost ceiling. When the remote provider supports cancellation, add a separate cancellation request for timeout/error cleanup. ## 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. ## External Actor Cost Guardrails For a third-party actor or scraper, bound three separate things: 1. the number of queries or partitions sent to the provider; 2. the provider's requested item limit and any remote run timeout; and 3. Breyta's polling attempts and elapsed time. Start with a small probe and inspect the provider run's actual event/item count before increasing target counts. A provider item limit can be a page size, per-query limit, or best-effort hint rather than a hard cap on work performed. ```clojure (let [input (flow/input) max-query-count 25 max-post-count 25 queries (vec (take max-query-count (or (:queries input) []))) max-posts (min max-post-count (max 1 (or (:max-posts input) max-post-count))) started-response (flow/step :http :start-search-actor {:connection :search-provider :method :post :response-as :json :path "/actor-runs" :json {:queries queries :maxPosts max-posts}}) started (:body started-response) run-id (:run-id started) terminal-result (try (flow/poll {:interval "15s" :timeout "10m" :max-attempts 40 :backoff {:type :exponential :factor 1.5 :max "60s"} :abort-on {:status #{400 401 403 404 429 500 502 503} :error? true} :return-on (fn [result] (#{"SUCCEEDED" "FAILED" "CANCELLED" "TIMED-OUT"} (:status result))) :id :search-actor-status-sleep} (let [status-response (flow/step :http :get-search-actor-status {:connection :search-provider :method :get :response-as :json :path (str "/actor-runs/" run-id)})] (:body status-response))) (catch Exception poll-error ;; Replace this provider-specific path/method with the ;; cancellation API for the actor service. (flow/step :http :cancel-search-actor {:connection :search-provider :method :post :path (str "/actor-runs/" run-id "/cancel") :retry {:max-attempts 1}}) (throw poll-error)))] (if (= "SUCCEEDED" (:status terminal-result)) terminal-result (throw (ex-info "Search actor did not succeed" {:status (:status terminal-result) :run-id run-id :result terminal-result})))) ``` The `:return-on` predicate includes every terminal status exposed by the provider, so the poll stops as soon as the actor finishes. The surrounding branch must then treat failure statuses such as `FAILED`, `CANCELLED`, and `TIMED-OUT` as failures (or route them to explicit cleanup); otherwise a terminal provider failure can be recorded as a successful Breyta run. The example limits Breyta's wait, not the remote run. `flow/poll` throws on a timeout or abort, so cleanup belongs in a `try`/`catch` around the poll; code placed only after the poll would never run on those paths. If the poll throws while the actor is still active, call the provider's cancellation endpoint (when available), record the remote run id, and inspect its terminal state before retrying. The `:cancel-search-actor` path and method above are provider-specific placeholders; make that request idempotent and use one attempt. If no cancellation API exists, use a smaller probe and provider-side quota/timeout controls; do not assume that a timed-out poll made the remote work stop. For high-volume search flows, define `target-count` as a requested output goal unless the flow contract guarantees an exact count. Keep these quantities distinct in the run result: - requested target count; - provider queries and per-platform candidate pools; - candidates evaluated and accepted leads. Discovery filters, duplicate removal, provider availability, and per-platform caps can make the accepted count lower than the request. Preserve explicit user-supplied ICP/role keywords; if the flow infers search terms, report the effective terms and allow an explicit override so high-signal role terms are not silently discarded. ## 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-06-15T10:08:01+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. 6. For public or paid installed apps with a manual run page, do not use `:supersede` unless "new run replaces old run" is intentional user-facing behavior. If users should be able to start another run while a previous run remains visible or continues working, prefer `:coexist`. ## 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. - Treating "only the latest installed-app run is visible" as a UI bug before checking whether `:singleton :on-new-version :supersede` intentionally caused the earlier run to be replaced. ## 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-06-16T12:30:05+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. JSON resources hydrate as parsed data by default. If the file itself is the payload for a downstream file-content API, request text explicitly: `:load {:package-lock :text}`. Use `json/write-str` only when you intentionally want to serialize parsed data back to JSON. ## Uploaded File To Downloadable Artifact Recipe Use this path when a user asks for "upload this file, process it, and give me a download." Examples include PDF review, transcript cleanup, CSV normalization, OCR plus LLM review, and generated Markdown, JSON, DOCX, or PDF exports. This is the uploaded-file-to-downloadable-artifact paved path. The important contract is: - accept the user file through a `:file` or `:resource` invocation input - keep uploaded files as resource refs until a step explicitly needs content - persist generated output as a retained blob resource - return a Markdown viewer with a `breyta-resource` `:view :download` embed - prove the browser/manual path with `breyta flows run --upload` Minimal flow: ```clojure {:slug :file-artifact-example :name "File Artifact Example" :description "Upload a file, generate a downloadable artifact, and show it in the final report." :invocations {:default {:label "Create downloadable artifact" :inputs [{:name :source_file :label "Source file" :type :file :required true :accept ["application/pdf" "text/*" "application/json"]} {:name :instructions :label "Instructions" :type :text :required false}]}} :interfaces {:manual [{:id :run :label "Create artifact" :invocation :default :enabled true}]} :flow '(let [input (flow/input) source-file (:source_file input) instructions (or (:instructions input) "Summarize the uploaded file.") report (flow/step :function :write-report {:input {:source_file source-file :instructions instructions} :code '(fn [{:keys [source_file instructions]}] (str "# Processed file\n\n" "Instructions: " instructions "\n\n" "Source resource: " (or (:uri source_file) (:resource-uri source_file) (get source_file "uri")) "\n\nReplace this function with OCR, LLM review, " "CSV cleanup, DOCX generation, or another " "domain-specific transform.")) :persist {:type :blob :filename "processed-file.md" :content-type "text/markdown"}}) report-uri (or (:uri report) (:resource-uri report))] {:breyta.viewer/kind :markdown :breyta.viewer/value (str "# Download ready\n\n" "The processed artifact is ready.\n\n" "```breyta-resource\n" "{:resource \"" report-uri "\"\n" " :view :download\n" " :title \"Processed file\"}\n" "```\n")})} ``` For a real PDF review flow, replace `write-report` with the processing steps: call an installed/public OCR app when OCR is needed, run the LLM review, then persist the corrected Markdown, DOCX, PDF, JSON, or other generated artifact as a blob. If a downstream function must read a persisted response or artifact, pass the whole persisted step result in `:input` and add that key to `:load`. Smoke test the same route the user will exercise: ```bash breyta flows run file-artifact-example \ --target draft \ --interface-id run \ --input '{"instructions":"Create a short corrected report"}' \ --upload source_file=./sample.pdf \ --wait breyta resources workflow list breyta resources read '' --limit 20 ``` Do not call the flow done until the final output renders a download affordance, the persisted artifact appears in `resources workflow list`, and `resources read` confirms the artifact content is not empty. ## 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: - `1500` 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 Operator maintenance for one existing resource: ```bash breyta resources search-index show "res://v1/ws//file/..." breyta resources search-index update "res://v1/ws//file/..." \ --text "customer interview transcript onboarding pricing objections" \ --source-label "Customer interview transcript" \ --tag transcript --tag interview \ --include-raw-content breyta resources search-index reindex "res://v1/ws//file/..." breyta resources search-index clear "res://v1/ws//file/..." ``` `update`, `clear`, and `reindex` are single-resource operations. They mutate or rebuild only the resource in the current workspace and then refresh that resource's search document. Use `--search-index-file ./index.edn` or `--search-index '{:text "..."}'` when the override is easier to manage as a map. 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-06-11T12:32:04+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 supports either child-flow items or named-agent items. | ## 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 ...}` or all items must be `{:type :agent ...}` - do not mix child-flow items, agent items, and legacy step items - effective concurrency must be at most `20`; 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 When an orchestration agent chooses which sibling specialists to run, prefer the agent-internal fanout tool in the [Orchestration Agent Fanout](/docs/guide-orchestration-agent-fanout) guide for the planner, named-agent fanout, and synthesis pattern. 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: - `1500` 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 ... :initial-interval-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: Orchestration Agent Fanout URL: https://flows.breyta.ai/docs/guide-orchestration-agent-fanout.md HTML: https://flows.breyta.ai/docs/guide-orchestration-agent-fanout Last updated: 2026-05-28T13:49:15+02:00 # Orchestration Agent Fanout ## Quick Answer Prefer agent-internal fanout when one coordinator agent should decide during its own reasoning which specialist agents to run in parallel. The preferred shape is: 1. Define reusable specialists in top-level `:agents`. 2. Give the coordinator agent `:tools {:fanout {:agents [...]}}`. 3. Let the coordinator call the generated `fanout_agents` tool with a bounded item list. 4. Let the coordinator synthesize the collected successes and failures into the final answer. Flow-level `flow/step :fanout` with `{:type :agent ...}` items is still valid. Use it when `:flow` already knows the sibling agent calls deterministically. Do not put full agent definitions inside fanout items. Both patterns reference top-level agents by id. ## Choose The Pattern | Situation | Use | | --- | --- | | A coordinator agent should decide whether and how to fan out while reasoning | Agent-internal fanout tool | | The parent `:flow` already has a deterministic item list | `flow/step :fanout` | | A specialist needs an adaptive one-at-a-time conversation with the coordinator | `:tools {:agents [...]}` direct delegation | | You want parallel raw tool calls rather than specialist agents | Wrap those tool capabilities in specialist agents or packaged steps, then fan out to those agents | | Each branch is a larger workflow or installed app boundary | `flow/call-flow` or `:fanout` `:call-flow` items | ## Preferred: Agent-Internal Fanout This example exposes a bounded `fanout_agents` tool to the coordinator. The model chooses which allowlisted specialists to call, but the runtime enforces the configured agent allowlist, item limit, concurrency cap, and error policy. ```clojure {:slug :repo-multi-agent-review :name "Repo Multi-Agent Review" :agents [{:id :orchestrator/review :description "Coordinate specialist reviews." :objective "Review the request and repo tree. When parallel specialist review is useful, call fanout_agents with the smallest useful item list. Then synthesize the collected successes and failures into a concise Markdown action plan." :input-schema [:map [:request :string] [:repo-tree :any]] :tools {:fanout {:agents [:review/security :review/performance] :max-items 5 :max-concurrency 3 :on-error :collect}}} {:id :review/security :description "Security reviewer." :objective "Review the selected area for security risks." :input-schema [:map [:request :string] [:repo-tree :any] [:area :string] [:focus {:optional true} :string]] :output {:format :json}} {:id :review/performance :description "Performance reviewer." :objective "Review the selected area for performance risks." :input-schema [:map [:request :string] [:repo-tree :any] [:area :string] [:focus {:optional true} :string]] :output {:format :json}}] :flow '(let [input (flow/input)] (flow/step :orchestrator/review :coordinate-review {:request (:request input) :repo-tree (:repo-tree input)}))} ``` The coordinator sees a tool shaped like: ```json { "items": [ { "agent": "review/security", "input": { "area": "auth", "focus": "OAuth callback and token storage" } }, { "agent": "review/performance", "input": { "area": "database queries" } } ] } ``` By default, each item inherits the coordinator agent's current `:inputs`, so the specialist calls above also receive `request` and `repo-tree`. Set `:inherit-inputs? false` when each fanout item should be fully self-contained. The fanout tool intentionally delegates only to the configured top-level agents; it does not expose arbitrary tool execution to the model. When a branch needs tool access, put those tools on the specialist agent definition. ## Flow-Level Named-Agent Fanout Use flow-level fanout when deterministic orchestration should build and run the whole batch without asking a coordinator model to choose it. ```clojure '(let [input (flow/input) fanout (flow/step :fanout :parallel-review {:items [{:type :agent :agent :review/security :input {:request (:request input) :repo-tree (:repo-tree input) :area "auth"}} {:type :agent :agent :review/performance :input {:request (:request input) :repo-tree (:repo-tree input) :area "database queries"}}] :max-concurrency 2 :on-error :collect})] fanout) ``` This is also the right pattern when a deterministic `:function` step has already shaped and allowlisted the item vector for the parent flow. ## Guardrails Use these defaults unless you have a specific reason not to: - Put reusable specialist behavior in top-level `:agents`. - Prefer the agent-internal fanout tool when the coordinator should choose the branches dynamically. - Use flow-level `:fanout` when the branch list is deterministic. - Keep the fanout tool's `:agents` allowlist small and explicit. - Use `:on-error :collect` when partial results are useful. - Keep `:max-items` and `:max-concurrency` below provider and workspace limits. - Pass compact inputs or resource refs, not huge inline payloads. - Put `:input-schema` on every specialist. - Use `:output-schema` or `:output {:format :json}` when downstream synthesis depends on structured specialist output. ## Result Handling The fanout tool returns the same collected result shape as `flow/step :fanout`: ```clojure {:successes [{:item ... :result ...}] :failures [{:item ... :error ...}] :total 2 :success-count 1 :failure-count 1} ``` With `:on-error :collect`, the coordinator can decide how to handle partial failures in its final answer. Typical synthesis behavior: - summarize successful findings - list failed specialist ids or areas - say whether the result is complete or partial - recommend reruns only for failed units ## Direct Named-Agent Calls When you do not need parallelism, call a named agent directly by using its qualified id as the step type: ```clojure '(flow/step :review/security :scan-auth {:area "auth" :repo-tree repo-tree}) ``` Inside an agent, publish the specialist directly with `:tools {:agents [...]}`: ```clojure {:id :orchestrator/review :objective "Delegate to the security specialist when needed." :tools {:agents [:review/security]}} ``` Use `:tools {:fanout ...}` only when the coordinator may need to launch several sibling specialists and collect them as one tool result. ## Common Mistakes | Mistake | Use Instead | | --- | --- | | Put a full agent config inside fanout items | Define it once in top-level `:agents` and reference it by id | | Give the model a broad or open-ended fanout surface | Configure `:tools {:fanout {:agents [...] :max-items N :max-concurrency N}}` | | Let the planner invent arbitrary agent ids | Use the configured fanout `:agents` allowlist | | Use flow-level `:fanout` when the coordinator should decide branches dynamically | Use the agent-internal fanout tool | | Use the fanout tool for one sequential specialist conversation | Use direct `:tools {:agents [...]}` delegation | | Fail the whole batch on one specialist failure | Use `:on-error :collect` and synthesize partial results | ## Related - [Step Agent](/docs/reference-step-agent) - [Step Fanout](/docs/reference-step-fanout) - [Flow Definition](/docs/reference-flow-definition) - [Flow Features And Config Toggles](/docs/guide-flow-features-and-config-toggles) - [Advanced Reliability](/docs/playbook-advanced-reliability) --- 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-08-09T16:31:23+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. Includes configured tool calls dispatched by `:agent` and execute-mode `:llm` steps. | | 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 workflow invocations | 60 | `flow/call-flow` invocations and async `:fanout` child runs per root 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 `:on-event` emissions | 500 | Per `:http` step attempt; streamed SSE event emissions past the cap are suppressed and counted. | | Max async child fanout concurrency | 20 | `:max-concurrency` must be at most `20`; validation rejects larger values. | | Async fanout item type | `:call-flow` or `:agent` | All items in async mode must use the same child workflow item mode. | | Mixed fanout modes | Not allowed | Do not mix legacy step items, `:call-flow` items, and `:agent` 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`). | | Terminal provider outcomes | Include success and failure statuses in `:return-on`, then branch on the result | Stop polling when the remote actor has finished and prevent terminal failures from being recorded as success. | ## External Actor Cost Boundary `flow/poll` limits the Breyta orchestration loop only. It does not impose a limit on work already accepted by a third-party actor or provider. Use these controls together: | Control | What it limits | What it does not guarantee | |---|---|---| | Query/partition count | Number of provider requests started by the flow. | That one provider request produces only one bounded run. | | Provider item limit (`maxPosts`, `limit`, `maxItems`) | Provider-specific requested output size. | A universal cap on actor events, pagination, or billing. | | Provider run timeout/quota | Provider-side execution where supported. | That the provider exposes the same semantics for every actor. | | `flow/poll` `:timeout` / `:max-attempts` | Breyta status checks and waiting time. | Cancellation of a still-running remote actor. | | Provider cancellation endpoint | Remote work after the flow detects timeout/error. | Cleanup when the provider has no cancellation surface. | For high-volume searches, run a small probe first, record the remote run id, inspect actual events/items and cost, then scale queries and item limits in stages. If a poll ends before the actor is terminal, cancel it when possible and do not blindly start a second run. ## 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. | ## API Request Validation Limits JSON request bodies sent to the flows API (CLI commands, `connections call`, manual run inputs, ...) pass a generic bounded-payload check before the handler runs. Violations return HTTP 400 with `violation` set to the failed bound (for example `max-string-length` with `at: map-value`). Exemptions: native connection webhooks skip JSON parsing entirely and are bounded only by the webhook payload caps above, and `flowLiteral`/`flow` command arguments are excluded from these checks (flow definitions have their own dedicated size limits, listed under Core Flow Limits). | Limit | Value | Notes | |---|---|---| | Max nesting depth | 8 | Webhook/event ingress and dashboard apply allow 10. | | Max total nodes | 20000 | Counts every key, value, and collection item. | | Max collection size | 1000 | Per map/vector/set. | | Max string length | 100000 chars | Per single string value (including map values). | | Max string length, `connections call` | 6000000 chars (~6 MB) | `POST /api/connections/:id/call` replays full outbound request bodies, so a single string value may be much larger. The whole request must still fit the ~8 MiB transport body cap. | Recovery: for oversized payloads on ordinary endpoints, persist the content as a resource and pass a ref instead of inlining it, or split the request. To replay a large HTTP request against a provider (for example to read a 4xx error body), use `connections call`, which accepts string values up to ~6 MB. ## 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. | | `Payload contains an overly long string` / `Payload is too large` (API validation) | Persist the content as a resource and pass a ref instead of inlining it.
Split the request into smaller payloads.
For replaying large HTTP requests against a provider, use `connections call` (~6 MB per string value). | | `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` / `fanout max concurrency exceeded` / `max fanout total items exceeded` | Reduce batch size or requested concurrency.
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. | | Remote actor continues after polling stops | Call the provider cancellation endpoint with the recorded run id when available.
Inspect provider terminal state before retrying.
Reduce probe size and configure provider-side quotas/timeouts when cancellation is unavailable. | ## 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-08-04T12:37:41+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](/docs/reference-docs-search) | | 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](/docs/reference-service-accounts) | | Flow-health incidents and digests | [Flow Health](/docs/reference-flow-health) | | Build or revise Marketing Hub dashboards | [Marketing Hub](/docs/guide-marketing-hub) | | Table resources and limits | [Table Resources](/docs/reference-table-resources) | | 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](/docs/reference-docs-search). | | `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 agent dashboard show [--pretty]` | Read the current Marketing Hub revision and complete governed manifest. `get` is an alias for `show`. | | `breyta agent dashboard catalog [--pretty]` | Read the allowed Marketing Hub widgets, bindings, actions, limits, and example manifest. | | `breyta agent dashboard apply --expected-revision (--manifest '' \| --manifest-file ) [--change-summary ""]` | Create the first Hub with revision `0`, or apply one complete manifest without overwriting a collaborator's newer revision. | | `breyta agent dashboard history [--limit 20] [--pretty]` | Read newest-first retained Marketing Hub revisions. | | `breyta agent dashboard restore --expected-revision --revision [--change-summary ""]` | Restore one retained Marketing Hub manifest as a new revision. | | `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 discover list` (`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 discover search ` (`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 auth api-connection [--capabilities ] [--new]` | Create or update the workspace's Breyta self-API runtime connection for `:http-api` slots. Default mode mints a durable workspace service-account key (survives web logouts, revocable, capability-scoped; default `resources.write,flows.run,runs.read`) and updates the existing runtime connection in place. Requires workspace creator/admin role. | | `breyta auth api-connection --oauth` | Legacy mode backed by the current login's refresh token. Stops working when that login's tokens are revoked (for example a web logout), so prefer the default service-account mode. Implies `force`, overwriting a service-account-backed connection. | | `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 public publish ` / `breyta flows public delist ` | Publish or delist a flow across marketplace visibility, Discover listing, and the public app page together. Requires explicit author approval before publishing. | | `breyta flows marketplace update --visible=true` | Lower-level marketplace visibility metadata update. Use `flows public publish/delist` unless intentionally changing only this flag. | | `breyta discover update --public=true` (`breyta flows discover update --public=true`) | Lower-level public Discover visibility metadata update. Use `flows public publish/delist` unless intentionally changing only this flag. | | `breyta flows update ...` | Update mutable flow metadata such as grouping, discover card media, display-icon selection, and publish description. | | `breyta flows init --name "My flow" [--input 'site-url:text:required:Site URL']` | Create the canonical local `flows/.clj` source and an empty schedules vector. Repeat `--input name:type[:required\|optional[:label]]` to seed manual invocation inputs. Local only unless `--push` is explicit. | | `breyta flows steps create --step-file ./steps/step.edn` | Add a qualified packaged step to the local source's top-level `:steps` vector. Use `--flow-file` when the source is not under the default path. | | `breyta flows steps update --step-file ./steps/step.edn` | Replace one existing local packaged step definition. | | `breyta flows steps remove ` | Remove one local packaged step definition; the command rejects removal while the local `:flow` still references it. | | `breyta flows schedules add --cron "0 9 * * MON" --timezone UTC` | Add a schedule map to the local source's top-level `:schedules` vector. Local only unless `--push` is explicit. | | `breyta flows schedules update --schedule-file ./schedule.edn` | Replace one complete local schedule map, preserving advanced fields not covered by shortcut flags. | | `breyta flows schedules remove ` | Remove one local schedule map without changing the flow body. | | `breyta flows steps run --params ''` | Run a packaged step from the complete local literal just in time on the server. This does not create or update a draft. `--timeout` (default 15m) bounds the request; extend it past the default for slow LLM or provider probes, for example `--timeout 30m`. The same flag applies to the `--run` mode of `flows init` and `flows steps create/update`. | | `breyta flows run-step --target draft --input '' --wait` | Run one named flow-body step through the normal draft runtime and draft-bound connection slots. This is the preferred authoring probe for inline function/code and bound LLM steps; pass the flow invocation's root input object. | | `breyta flows compose --body-file ./flows/.body.clj` | Replace only the quoted top-level `:flow` form, preserving packaged steps, interfaces, schedules, functions, and providers. | | `breyta flows pull --out ./tmp/flows/.clj` | Pull definition to local file. Use this for an existing remote flow; `flows init` is the local-first path for a new one. | | `breyta flows lint --file ./tmp/flows/.clj [--local-only\|--server] [--timeout 30s]` | Lint candidate source before push. Local lint is offline and fast: missing qualified packaged-step references, `flow/step` shape errors mirroring push validation (`flow_step_arity_invalid`, `flow_step_missing_config`, `flow_step_missing_step_id`, `flow_step_packaged_extra_argument`), and a warning for packaged steps never referenced from `:flow` (`unreferenced_packaged_step`). Shape checks cover plain-literal forms only; forms using reader macros or runtime-computed arguments are skipped by design. Server lint is canonical, non-mutating, and bounded by `--timeout`. | | `breyta flows push --file ./tmp/flows/.clj [--timeout 2m]` | Explicitly persist the local working copy as the workspace draft and validate it by default. `--timeout` defaults to two minutes per request and applies separately to draft save and validation, so total command time can exceed the flag value. If a request times out, verify with `breyta flows show ` or `breyta flows validate ` before retrying because the draft may already be saved. | | `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 connection slots, secret slots, and activation 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 --target draft --input '{}' --wait` | Run a brand-new or legacy draft explicitly. A single enabled legacy manual trigger is selected automatically; use `--trigger-id ` when several manual triggers exist. | | `breyta flows run --invocation --input '{...}' --wait` | Start a specific invocation contract directly. | | `breyta flows run --interface-id --upload field=./file.pdf --wait` | Upload a local file as a workspace file resource and pass it as a manual `:file`/`:blob-ref` invocation input. Repeat `--upload` for multi-file inputs. | | `breyta flows run --buyer-test --installation-id --wait` | Run a specific paired Buyer Test installation. Use from the Buyer Test workspace after creating the paired source install; the server derives Buyer Test behavior from the current workspace and installation, and `--buyer-test` cannot be combined with `--target`. | | `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 '' [--timeout 5m]` | Run one step with draft context. Output is compact and includes a binding preview; increase `--timeout` for slow LLM/provider calls. | | `breyta steps run ... --params-file ./params.json --result-path rows.0` | Run one step with larger params and focused preview. For flow-local templates, pass `{"template":"","data":{...}}` and keep `--flow`/`--source` set. 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 show --include-result [--installation-id ]` | Read the run's final output: the full result in a bounded envelope without step captures. The canonical output readback after `flows run --wait`. | | `breyta runs events [--step ] [--installation-id ] [--limit ]` | Inspect a bounded run and step event timeline for live debugging. | | `breyta runs inspect [--step ] [--installation-id ]` | Inspect a run with compact step data, or inspect one step's input/output/error/cost without expanding the whole run payload. For an installed run, pass `--installation-id ` (it is in every `runs list` row and the run-start response); without it the run resolves only in the current workspace and returns `Run not found`. As the installer you see run status, timing, final output, and artifacts only; the creator's internal steps are not exposed (no step list/previews/captures, and the response marks `stepsRedacted`). | | `breyta runs step [--full] [--installation-id ]` | Short alias for compact step I/O inspection, including the step event timeline when available. Use `--full` for captured output/error payloads. For an installed run you inspect as the installer, step internals are not available (creator IP); use `runs inspect --installation-id ` for the run's status and final output instead. | | `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 mcp config --workspace-id --provider [--transport stdio\|http]` | Print workspace-bound MCP client config for direct HTTP MCP or the CLI stdio proxy. `breyta mcp init` is an alias. | | `breyta mcp stdio --workspace-id [--token-env-var ] [--read-only] [--toolsets ]` | Run a stdio MCP proxy that bridges to the workspace MCP HTTP endpoint. | | `breyta mcp doctor --workspace-id [--token-env-var ] [--read-only] [--toolsets ]` | Verify MCP initialize and `tools/list` reachability without invoking mutating tools. | | `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] [--format json]` | List persisted resources with picker-style filters. | | `breyta resources search "" [--limit 10] [--type result|file] [--mode lexical|hybrid|semantic] [--keyword-mode all|balanced|any]` | Search workspace files and persisted blobs. Use `--keyword-mode balanced` for natural-language questions over small resource sets. 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 agent work mark --message-id --body "..." --dedupe-key [--subject "..."]` | Mark the existing proactive card actionable in chat; unseen cards email after 30 minutes, while passively viewed unresolved marketing work receives one reminder digest after 24 hours. | | `breyta agent work classify --proactive-message-id --classification unfinished\|new-initiative\|nothing [--title "..."] [--summary "..."]` | Record the structured result of the current proactive triage turn. | | `breyta agent work status --proactive-message-id --state active\|blocked-on-user\|completed [--summary "..."] [--blocker "..."]` | Record the structured terminal state of a bounded proactive continuation turn. | | `breyta agent settings update --continue-unfinished-work=true\|false` | Allow or prevent safe unfinished work from continuing automatically. New initiatives always wait for explicit approval. | | `breyta agent email send --proactive-message-id --body "..." --dedupe-key [--subject "..."]` | Make the existing proactive card email-eligible if it remains unseen for 30 minutes. | | `breyta feedback send --title "..." --description "..."` | Send issue/feature feedback to the Breyta team. | ### Proactive Work Lifecycle The hosted-agent system turn supplies the exact proactive message id and owns whether the turn is continuation triage or new-initiative discovery. Do not infer approval from conversational wording and do not substitute another id. After read-only triage, record exactly one classification: ```bash breyta agent work classify \ --proactive-message-id \ --classification unfinished \ --title "Finish the campaign draft" \ --summary "The approved draft still needs its final audience segment." ``` After a bounded automatic or user-approved continuation, record its state before the turn ends: ```bash breyta agent work status \ --proactive-message-id \ --state blocked-on-user \ --summary "The draft is ready." \ --blocker "Waiting for release approval." ``` `--continue-unfinished-work=true` authorizes safe slices of already-approved unfinished work. It never authorizes a new initiative: those are surfaced for the user's explicit **Go ahead** first. ### Mark Proactive Work Use `breyta agent work mark` only after investigating a scheduled work-ping and finding something actionable and useful. Pass the exact proactive message id: ```bash breyta agent work mark \ --message-id work-ping-ws-user-2026-07-30-1 \ --subject "I found the failed import" \ --body "The import is missing an account id." \ --dedupe-key failed-run:customer-import ``` The identity keeps the visible card, seen state, and fallback delivery on one record. `proactive_agent.work.mark` is the API command identifier, not a dotted CLI subcommand. The legacy `agent email send` and `proactive_agent.email.send` names remain compatibility aliases. ### 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](/docs/reference-service-accounts) - 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](/docs/reference-flow-health) - Storage filters, table-resource behavior, partitioning, and table-family limits: [Table Resources](/docs/reference-table-resources) - 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 your installations for a flow. | | `breyta flows installations list --all` | List installed-app inventory visible in the current workspace. Add a positional `` before `--all` to filter that workspace inventory to one flow. | | `breyta flows installations list --all-users` | Creator-only view of installations across all consumer users for a flow. | | `breyta flows installations create --name "..."` | Create an installation for a workspace flow or a unique Discover-listed public flow slug after any required paid checkout/trial entitlement exists. | | `breyta flows installations create --source-workspace-id --source-flow-slug ` | Disambiguate a public Discover source when more than one source uses the same slug. | | `breyta flows installations create --source-workspace-id --source-flow-slug --public-app-funnel-id ` | Preserve public app funnel attribution on the installation and any checkout action returned by creation. | | `breyta flows installations create --source-workspace-id --source-flow-slug --local-private-test` | Local dev only: test a private cross-workspace install without public visibility. | | `breyta flows installations create --buyer-test-source-install --source-workspace-id --source-flow-slug ` | Creator Buyer Test Mode install from the Buyer Test workspace for exercising a paid/public source app without consuming real buyer entitlement. | | `breyta flows installations get ` | Inspect installation state (`installedVersion`, `latestAvailable`, `updateAvailable`, `policy`) and public-app `runReadiness` when billing/trial entitlement is known. | | `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 ` | | Paired Buyer Test installation | `--buyer-test --installation-id `; requires an existing Buyer Test source install | | Explicit release version | `--version ` | | Runtime input payload | `--input '{...}'` or `--input-file ./input.json` | | 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`. For older drafts that still use `:triggers`, the same command remains usable: one enabled manual trigger is selected automatically, including a trigger that has no authored `:id`. Use `--trigger-id` when the draft has more than one manual trigger. Prefer the canonical `flows run` command over the older `flows draft run` shorthand when you need `--invocation`, `--interface-id`, or legacy trigger selection guidance. `--input` is the per-run payload sent to `flow/input`. Runs against a configured profile or installation can also merge saved setup/activation inputs into `flow/input`, but ad hoc CLI runs do not synthesize source-level `:requires` defaults. If a test or approval run depends on exact copy, metrics, IDs, or mode flags, pass those per-run values explicitly or use `--input-file ./input.json` for saved or large input JSON. 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](/docs/reference-docs-search) 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](/docs/reference-docs-search) - [Flow Metadata And Discover](/docs/reference-flow-metadata) - [Flow Health](/docs/reference-flow-health) - [Flow Definition](/docs/reference-flow-definition) - [Jobs Control Plane](/docs/reference-flow-jobs) - [Service Accounts](/docs/reference-service-accounts) - [Table Resources](/docs/reference-table-resources) - [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-07-25T11:16:17+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 stored metadata. It can also be authored in the flow file with `:publish-description` and then persisted on push. - 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" ``` - You can also author the same markdown in source as `:publish-description "## Install this flow"` and persist it with `breyta flows push`. - 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 discover list breyta discover search billing breyta discover update billing-sync --public=true ``` Important distinctions: - `breyta 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. For app-owned plan catalogs, put `:paywall {:when ...}` on each plan when timing is plan-specific; a root `:paywall` under `:monetization` remains supported as a shared default. - `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. - `plans[].visible`: set to `false` to keep an existing plan stored but hide it from new public plan selection. - `plans[].retired`: set to `true` to keep an existing plan stored but remove it from new public plan selection. Use this for obsolete plan IDs that historical checkout, entitlement, billing, or audit records may still reference. - Runtime form inputs do not automatically select billing plans. A buyer is entitled to the plan selected during checkout/install, or to the default visible plan when no explicit plan is supplied. Matching an input value to a `plan-id` by string convention is not a supported billing selector. - 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 App FAQs Public app FAQs are author-owned facts. Define them under `:marketplace :app` when a public app page needs an FAQ: ```clojure {:marketplace {:app {:faqs [{:question "Do I need to connect my own Apify account?" :answer "No. This app uses the author's Apify connection."} {:question "What does this app return?" :answer "It returns the scraped reel data described in the app output."}]}}} ...} ``` These items are used by the Discover app detail and the public marketing page, including its `FAQPage` structured data. Breyta does not generate FAQs when this field is omitted. Omit it when an app has no FAQ. To remove an FAQ that was published before, set `:faqs []` and republish. Keep setup claims consistent with each connection's `:provided-by` value. In particular, do not tell installers to connect a service that the author provides. ## 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, authored FAQs, and discover card media when the public surface should be polished: - Install dialog markdown: `breyta flows update --publish-description-file ./publish-description.md` - Public app FAQs: `:marketplace {:app {:faqs [{:question "..." :answer "..."}]}}` - Discover card hero: `breyta flows update --publish-media-type image --publish-media-source-kind https-url --publish-media-source https://...` 5. Choose one public visibility path: - Source-first: add both `:marketplace {:visible true}` and `:discover {:public true}` before push only after explicit approval. - Explicit after release: run `breyta flows public publish ` after the flow has been pushed, released, and explicitly approved. 6. Push the flow. 7. Release or promote so there is an installable live version. 8. If you chose the explicit public path, run `breyta flows public publish ` after release. 9. Verify it in the web Discover UI, or with `breyta discover list` / `breyta discover search `. 10. 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-08-04T10:56:17+02:00 --- # Breyta CLI Skill 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. When a hosted proactive system turn supplies a `proactiveMessageId`, preserve that exact id. Finish read-only triage with `breyta agent work classify` and finish an approved or automatic continuation slice with `breyta agent work status`. Never infer approval from reply wording. The `--continue-unfinished-work` preference applies only to already-approved unfinished work; new initiatives always wait for explicit user approval. ## 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/reuse | visibility, pricing, copy, Discover, install surface, reusable public/paid app capability | `playbooks/public-and-marketplace.md`; add `references/public-app-pricing.md` when pricing or paid plans are involved | | 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/public-app-pricing.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 - Before creating a persistent table in an existing workspace, load `references/outputs-and-tables.md`, inventory related tables, and trace each candidate's grain, key, writers, and consumers. Designate one canonical logical current-state table or partitioned family per durable entity and extend or migrate it by default. Do not create a flow- or campaign-specific copy of the same entity merely for convenience. - Build small draft slices: contract, one manual interface, one meaningful boundary, lint, push, configure check, run, read output. - Build one step at a time: after each new or changed step, run just that step and read its actual output before writing the next one. Use `breyta flows steps run --params ''` for packaged steps in the local source (request timeout defaults to 15 minutes; use `--timeout` for slower work), `breyta steps run --type --id --params ''` for inline activity steps, or `breyta steps run --flow --source draft ...` against a pushed draft; prove `:sleep`, `:wait`, `:fanout`, and control-plane steps with a minimal flow run instead. Do not write five steps and then debug the composite. - Use `breyta flows run-step --target draft --input '{...}' --wait` as the canonical probe for named inline function/code steps and LLM steps that rely on draft-bound connection slots. Pass the invocation's root input shape. Reserve `flows steps run` for qualified top-level packaged `:steps`; `steps run --flow` is a lower-level primitive/config probe. - When the intent is a public app (installable, Discover, or marketplace), design the end-user setup screen before the flow: at most one connection card per provider account and origin plus at most one required text field, then derive the flow from that screen. Load `playbooks/public-and-marketplace.md` and `references/public-flows.md` before writing source. - 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. Local lint is offline and also reports missing qualified packaged-step references. Use `--local-only` for fast checks or `--server` when canonical pre-push checks are required; add `--timeout ` when server lint needs more than the default 30 seconds. - For a new flow, start with `breyta flows init `; repeat `--input 'name:type[:required|optional[:label]]'` to seed invocation inputs. It creates the canonical local source with a manual `run` interface (no-input only when `--input` is omitted) and empty `:schedules`. For an existing flow, use `breyta flows pull `. - Use `breyta flows steps create/update/remove` to edit only the top-level packaged-step vector; removal is rejected while the local `:flow` references the step. Use `breyta flows schedules add/update/remove` to edit only the top-level `:schedules` vector, using `--schedule-file` for complete-map replacement when advanced fields are present. Use `breyta flows compose` to edit only the quoted `:flow` body. Use `breyta flows steps run ...` to execute a packaged step from the complete local literal without persisting a draft. Its `--timeout` flag also applies to the `--run` modes of `flows init` and `flows steps create/update`. Push explicitly with `breyta flows push`; command-level `--push` flags are opt-in convenience, not implicit remote writes. Local lint also reports missing qualified packaged-step references; server lint/push remains canonical. - `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. - For file/document transformation workflows, follow the uploaded-file-to-downloadable-artifact paved path: declare `:file` or `:resource` inputs, keep uploaded files as resource refs, persist generated artifacts with `:persist {:type :blob ...}`, return Markdown with a `breyta-resource` `:view :download` embed, and prove the manual path with `breyta flows run --upload`. - 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. - For app requests, show a visible reuse checklist before editing source, duplicating a template, or asking for direct provider credentials. Include: needed outputs/functions; Discover searches tried; relevant paid apps found; and selected/rejected paid apps with reasons. - Search Discover public and paid installable apps by requested outcome, business function, and output modality before building from scratch. For multimodal apps, check every needed function separately, including text-generation apps, image-generation apps, video-generation apps, and any relevant paid app. For an agent that creates ads, example searches include `ad generator`, `copywriting`, `text-generation`, `OpenAI image generation`, `image-generation`, and `social ad creative`. - To evaluate one Discover hit in depth, use `breyta flows discover show /` (full public listing: publish copy, pricing, connections, versions) or open the hit's `public_app_url`. Do not run `breyta flows show` against another workspace's flow; that requires membership in the owning workspace and fails with access denied. - Treat every non-placeholder `.secret` configuration value as an intentional plaintext create or replacement under the slot's workspace secret ref. The documented `:generate` sentinel requests server-side generation. Never pass a secret ID or `:secret-ref` name as the value to reuse it. Omit the secret field only to preserve an already-bound target value; omission does not bind an unconfigured draft target. Then run `flows configure check` and a provider-specific, read-only smoke test. - For installable apps, keep shared platform credentials `:provided-by :author` and each customer's account or OAuth connection `:provided-by :installer`. Verify a fresh installation does not expose the author-owned secret. - 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, visible reuse checklist, Discover searches tried, relevant paid apps found, and selected/rejected paid apps or patterns with reasons. 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, install-shaped behavior passed the install test matrix in `playbooks/release-and-install.md` (minimal-config install, plus per declared surface: optional integrations absent and broken, explicit-false toggles, malformed constrained text), 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`; for an installed-profile run, add `--installation-id `; add `--step ` for one step. 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-07-27T11:46:30+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. **Local skeleton:** for a new flow, run `breyta flows init `. It creates the canonical local `.clj` source with one no-input manual `run` interface and an empty `:schedules` vector. Use `breyta flows pull` for an existing remote flow. Add packaged definitions with `breyta flows steps create/update/remove`, add source-owned schedules with `breyta flows schedules add/update/remove`, and use `breyta flows compose` to edit only the quoted `:flow` body. These commands edit the same local file and do not write remote state unless `--push` is explicit. 4. **Small slice:** lint the local source and run a packaged step directly from the complete literal with `breyta flows steps run --params ''`. This is ephemeral server execution and does not persist a draft. Push explicitly with `breyta flows push` when the source is ready to become the workspace draft. The command-level `--push` flags are explicit convenience, not an implicit remote write. 5. **One boundary at a time:** add one connection, provider call, table, job, agent, or child flow at a time; run the new step in isolation and read its actual output, then push, configure-check, run, and inspect before adding the next boundary. 6. **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. 7. **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. 8. **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`. 9. **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. For app requests that can be composed from public or paid apps, show a visible reuse checklist before source edits, direct provider setup, or template duplication: needed outputs/functions; Discover searches tried; relevant paid apps found; and selected/rejected paid apps with reasons. Search relevant Discover public and paid installable apps by requested outcome, business function, and output modality with `breyta flows discover search ""`, then search approved templates only for the wrapper or missing primitive. For multimodal requests, check every needed function explicitly, such as text-generation, image-generation, search, lead research, video, email, and any relevant paid app. For an agent that creates ads, example searches include `ad generator`, `copywriting`, `text-generation`, `OpenAI image generation`, `image-generation`, and `social ad creative`. Do not fall back to direct OpenAI, Anthropic, Gemini, image, search, email, or other provider setup while a suitable installed public or paid app can provide the capability faster. 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`. Add `--keyword-mode balanced` for natural-language questions over small resource sets, then 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 use `--input-file ./input.json` for large payloads that should not go through shell arguments. Call an authored HTTP interface with `breyta flows interfaces call --input '' --wait`. `breyta flows run --input` sends the JSON supplied to that run as the per-run payload. Runs against a configured profile or installation can also merge saved setup/activation inputs into `flow/input`, but ad hoc CLI runs do not synthesize source-level `:requires` defaults for you. Put values that change per run under `:invocations`; keep connections, secrets, and setup/config values in `:requires`. For approval or smoke-test runs that must prove exact copy, metrics, IDs, or mode flags, use `--input-file` with a saved JSON file or pass those per-run values explicitly every time. 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` (or `--keyword-mode balanced` for natural-language questions), `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: Marketplace App Pricing URL: https://flows.breyta.ai/docs/breyta-cli-skill-public-app-pricing.md HTML: https://flows.breyta.ai/docs/breyta-cli-skill-public-app-pricing Last updated: 2026-05-29T09:48:02+02:00 # Marketplace App Pricing Load this reference when helping a creator price a paid public app, paid marketplace flow, app-owned plan catalog, prepaid run pack, subscription, subscription usage plan, trial runs, or public app cost model. ## Default Stance Prefer prepaid usage packs and subscriptions. Do not recommend tiny payments or per-run microtransactions as the default Breyta marketplace model. Breyta paid apps can have low operational overhead. The main variable costs are payment fees, Breyta platform fees, run costs, external API or LLM costs, and creator support burden. Use those actual costs to check viability, but price from buyer value first. Use pricing guidance as advisory. Do not block publishing unless product policy or release checks already enforce a hard rule. ## Pricing Model Choice Pick the pricing model from the buyer's usage pattern: | Buyer pattern | Recommended model | Why | | --- | --- | --- | | Recurring business outcome | `:subscription` | Simple recurring access for ongoing value | | Recurring access with measurable run volume | `:subscription-usage` | Includes successful runs each billing period | | Irregular but repeatable usage | `:usage` prepaid pack | Avoids tiny transactions and gives buyers a run balance | | Fixed meaningful utility | `:one-time` | Works only when the price is high enough to justify checkout and support | | Lead magnet, demo, or sampling | `:free` or trial runs | Better than a tiny paid checkout | Do not describe `:usage` as postpaid pay-as-you-go. In Breyta it is a prepaid run pack: the buyer pays once for a fixed number of successful runs. Do not describe `:subscription-usage` as having paid overages; included runs reset on renewal and exhaustion blocks more runs until renewal or a future product change. ## Cost Floor Estimate the cost floor before recommending a price: 1. Get completed-run cost data when available. 2. Include configured `:metering` costs for paid API, enrichment, tool, search, or provider steps. 3. Include LLM token estimates when Breyta has provider/model pricing. 4. Add external API or LLM costs the creator pays outside Breyta. 5. Add payment fee and Breyta platform fee assumptions. 6. Add a support burden assumption when the app requires setup help, judgment, or ongoing customer handling. 7. Flag any unknown-cost line items. Use `:metering` and run cost estimates for creator cost visibility. Do not imply step metering is buyer billing. ## Recommendation Heuristics - Price from buyer value first, then verify the cost floor. - Treat Breyta operational overhead as low by default. - Prefer packs and subscriptions over small one-time charges. - If transaction fee drag is meaningful, bundle more runs or switch to a subscription. - If per-run cost is unknown, recommend a smaller trial cap, smaller included run quantity, or prepaid pack until real runs are measured. - If support burden is high, favor a subscription. - If usage is frequent and predictable, favor subscription usage. - If usage is irregular but repeatable, favor prepaid usage packs. - If the app is mostly a demo or lead magnet, use free or trial runs instead of tiny paid checkout. ## Starting Package Examples Use concrete packages when the creator has no pricing anchor: | App pattern | Recommended starting package | | --- | --- | | Recurring report, monitor, or alert | `$49/mo` with 100 included runs and five trial runs | | High-value AI review or enrichment | `$99/mo` with 50 included runs and a smaller trial cap | | Irregular batch utility | `$99` prepaid pack with 500 successful runs | | Fixed diagnostic or setup utility | `$199` one-time purchase when the result is valuable enough to justify checkout | | Demo or lead magnet | Free access or five trial runs | If the creator suggests a tiny per-run checkout, bundle it into a prepaid pack or subscription. Example: replace `$1/run` with `$49` for 100 runs, `$99` for 500 runs, or `$49/mo` with included runs. ## Margin Worksheet For each recommendation, estimate: 1. Buyer price 2. Breyta platform fees and payment fees 3. Run cost at full included usage 4. External API, search, enrichment, or LLM costs 5. Support allowance 6. Remaining gross margin Example: a `$99` prepaid pack with 500 runs is `$0.198/run` before fees. If estimated run and external costs are `$0.04/run`, full usage costs about `$20`. That leaves `$79` before fees and support. If the margin is too thin, reduce included runs, raise the pack price, or move to a subscription. ## Agent Output When advising on a paid marketplace app, include: - recommended pricing type and rationale - suggested price, currency, included runs, and trial runs - estimated cost per successful run - estimated gross margin at expected usage levels - fee-drag warning when the proposed price is too small - one recommended package and, when useful, one alternative package - risks: unknown step costs, AI-heavy costs, external API costs, support burden - source-authored plan shape when needed, using app-owned monetization: ```clojure :marketplace {:visible true :app {:app-id "example-app" :app-name "Example App" :app-primary-flow-slug "example-app" :app-flow-slugs ["example-app"] :monetization {:plans [{:plan-id "starter" :name "Starter" :default true :pricing {:type "subscription-usage" :amount 49 :currency "usd" :interval "month" :unit "run" :included-quantity 100} :trial {:runs 5}} {:plan-id "run-pack" :name "Run Pack" :pricing {:type "usage" :amount 99 :currency "usd" :unit "run" :included-quantity 500}}]}}} ``` Publishing paid settings, Discover visibility, marketplace visibility, release, and public install-facing claims still require explicit flow-author approval. --- 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-07-29T18:26:20+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. ## Canonical Table Ownership Treat persistent workspace tables as shared data products, not flow-local scratchpads. Before creating one: 1. Inventory related table resources and flows that read or write tables. Use indexed listing only as a convenience scan, then verify the result against the canonical resource store. 2. Read representative rows from plausible candidates. 3. Trace each candidate's grain, key, schema, writers, scheduled rebuilds, and consumers. Verify whether its writers preserve fields they do not own. 4. Classify the table as canonical current state, source/campaign data, derived output, or append-only audit history. 5. Designate one system of record for each durable entity grain that multiple flows must coordinate around. Use bounded page sizes for discovery: ```bash # Fast indexed candidate scan. This is not authoritative on its own. breyta resources list --workspace --accept application/vnd.breyta.table+json --limit 100 # Authoritative resource-store scan. Keep result resources whose contentType is # application/vnd.breyta.table+json. breyta resources list --workspace --type result --limit 100 # Catalog/list cross-check. Repeat with --cursor while meta.hasMore is true, # but do not treat this projection-backed listing as authoritative on its own. breyta flows list --workspace --include-archived --limit 100 --page-size 100 breyta flows list --workspace --include-archived --cursor --limit 100 --page-size 100 # Direct source reads for every returned flowSlug and every configured target. breyta flows show --workspace --target live --full breyta flows show --workspace --target draft --full # User-visible installed-app candidate scan. Authenticate as a service account # with installations.read for a workspace-wide inventory. breyta flows installations list --all --workspace breyta flows installations show --workspace # Exact source read for a local installation only: sourceWorkspaceId is absent # or equals the consumer workspace. Linked-app full source is provider IP and is # not available through the installer CLI. breyta flows show --workspace --version --full # Optional index-backed candidate scans. These are not authoritative on their # own. breyta flows grep ":type :table" --or "flow/step :table" --workspace --scope workspace --target live --limit 100 --from 0 breyta flows grep ":type :table" --or "flow/step :table" --workspace --scope workspace --target draft --limit 100 --from 0 breyta flows grep "" --workspace --scope workspace --target live --limit 100 --from 0 breyta flows grep "" --workspace --scope workspace --target draft --limit 100 --from 0 breyta resources read ``` Do not treat the indexed `--accept` listing or the first page as complete. For the authoritative `resources list --type result` scan, continue with `--cursor ` while `has-more` is true and retain every item whose `contentType` is `application/vnd.breyta.table+json`. Do not add `--accept` or `--query` to this verification pass because those select the search-backed picker path. Treat `flows list` as a catalog-backed cross-check, not an authoritative flow-store enumeration. Continue with `--cursor ` while `meta.hasMore` is true, require the cursor to advance, and collect every returned `flowSlug`. For every returned slug, run `flows show --full` separately with `--target live` and `--target draft`, then inspect `data.flowLiteral` for table reads, writes, and persisted table references. A `Draft not found` response or the CLI's `draft target is not configured` error records the draft target as absent. A `no_active_version` code, `Flow has no active version` response, or the CLI's `live target is not configured` error records the live target as absent. Any other unreadable target or listing error makes the cross-check incomplete. Process one flow/target at a time so large definitions do not accumulate in memory. Archived flows are not active writers, but include them to recover prior ownership and migration contracts. Before authorizing a new table, require either an operator/admin-provided direct flow-store enumeration or proof that the flow catalog has been reconciled against the flow store for this inventory. If neither is available, report the flow inventory as incomplete and do not create a new table. Exhausting `flows list` alone cannot prove that every authored flow was returned. Also inventory installations for the consumer workspace. A normal user's `flows installations list --all` result is filtered to that user and is not a workspace-wide inventory. Run it as a service-account principal with `installations.read`, use an operator/admin direct profile-store inventory, or prove that every workspace member's installation inventory has been included. If none is available, report the installation inventory as incomplete and do not create a new table. The workspace installation endpoint is paginated to 100 items and returns `data.nextCursor` with `data.hasMore`. The current CLI command exposes the first page but no installation cursor flag. If `data.hasMore` is true, use an operator/admin API or store path that accepts `data.nextCursor`, require the cursor to advance, and continue until `data.hasMore` is false. Do not repeat the same first-page CLI command or call that result complete. For every returned item, retain its `profileId`, `flowSlug`, `sourceWorkspaceId`, `sourceFlowSlug`, owner when available, enabled state, and exact installed `version`; inspect details with `flows installations show `. When `sourceWorkspaceId` is absent or equals the consumer workspace, read the local exact version with `flows show --version --full`. When `sourceWorkspaceId` differs, the installation is linked. Do not run the consumer-workspace `flows show` command: it can select an unrelated same-named local flow, and the installer's supported read is intentionally a redacted summary rather than the provider's full source literal. Account for the linked item as an opaque potential writer with its exact pinned metadata. For any table grain that may overlap the app's purpose or observed resources, require a source-authorized operator/provider table-ownership contract for that version; otherwise fail closed for that grain. An opaque linked app does not make an unrelated grain incomplete when the non-overlap is explicitly documented. Enabled installations are potential current writers; disabled installations still carry prior ownership and migration history. Use `flows grep` only to accelerate candidate review, never to choose the only flows whose sources are inspected. Run every grep query separately with `--target live` and `--target draft`; the default `latest` target can hide an active live writer when an unpublished draft exists. Before using any grep page, inspect `timedOut` in both the result and metadata. It must not be `true`; an absent field is the normal non-timeout response. Discard a timed-out partial page and retry with a narrower pattern or surface. On a non-timed-out page, continue with `--from `, advance by the returned `size`, and stop when the page returns fewer results than the requested `limit`. Only consider the inventory complete after the authoritative resource-store scan has `has-more` false; the direct flow-store inventory or catalog reconciliation proof is complete; every returned authored slug's configured live/draft sources have been read; every installation inventory page and owner has been accounted for; every local installed source has been read; and every linked installation has either a resolved ownership contract for the relevant grain or a documented non-overlap. Exhausted `flows list` and grep pages are useful cross-checks, not proof of flow-store completeness. Use this ownership model: | Table role | Policy | |---|---| | Canonical entity/current state | Keep one logical shared table or partitioned family per entity grain. Upsert by an immutable internal entity id. | | Source or campaign data | May remain separate, but reference the canonical entity id and do not make global decisions independently. | | Event or audit ledger | Keep immutable history separately, ingest idempotently by a stable source event id, and reconcile relevant state into the canonical entity row. | | Replacement table | Use only as a bounded migration. Name the old and new authority and define coexistence and retirement. | Extend or migrate the canonical table by default. Do not create a new table merely because the flow, campaign, or source is new. A separate table is justified when the data has a genuinely different grain or lifecycle, requires different security or retention, or is part of a documented migration. Do not reuse a table based only on its name: an incompatible key or a scheduled writer that rebuilds a narrow schema can silently lose fields and recreate duplicates. ### CRM Pattern - Keep one canonical contact table or partitioned table family shared by all lead research, outreach, reply, and monitoring flows in the workspace. - Generate an immutable internal `contact_id` once. Treat LinkedIn ids, emails, and profile URLs as mutable aliases, never as the canonical row key. Resolve aliases through an explicit matching and merge process; preserve the surviving internal id when an alias changes or duplicate people are merged. - Put alias matching, canonical-id allocation, and alias attachment behind one enforceable identity-resolution boundary before a contact becomes eligible to send. Either start one dedicated resolver flow through a concurrency-aware top-level HTTP/interface/event or API invocation with `:concurrency {:type :singleton :on-new-version :drain}` and no direct contact-id creation elsewhere, or use a transactional external store with a uniqueness constraint for each provider/account/alias-type/normalized-value key. Never invoke the resolver with `flow/call-flow` or another child-flow path: child execution does not enter the top-level active-lane/drain coordinator. Prove top-level behavior with `concurrencyDecision`, run timing, and the run list. Inside that boundary, normalize and re-read every supplied alias, reuse or merge the winning contact, and allocate a new `contact_id` only when none exists. A serialized table-backed resolver must verify all deterministic alias-index writes before marking the contact eligible. On a uniqueness conflict, partial write, rejection, timeout, or unknown result, re-read and reconcile; never return both candidate contacts as send-eligible. - Plan capacity before a concrete table reaches `50_000` live rows. Keep one logical CRM by using first-class `:partitioning` with a deterministic bounded bucket such as `hash(contact_id) mod N`, where `N` is at most `128`. Store the bucket in a field such as `contact_partition`, use a `:field` partition strategy, and compute the same bucket for every point read or write. Use an external database instead when identity matching or eligibility requires broad cross-partition scans. - An ordinary canonical-table upsert is not a lock. Never use resolve/upsert -> eligibility check -> claim across independent sending flows because two runs can both pass the check. - Put every first-touch side effect behind one enforceable serialization boundary. Either start one dedicated sender flow through a concurrency-aware top-level HTTP/interface/event or API invocation with keyed concurrency on the raw immutable `contact_id` and `:on-new-version :drain`, with no other flow allowed to send directly, or use a transactional external store/provider that conditionally creates a unique policy-scoped send key such as `first-touch:`. Never use `flow/call-flow` or another child-flow path to enter this sender lane. Prove the top-level start's `concurrencyDecision` and `concurrencyKey`. Inside the serialized sender lane, re-read eligibility and persist the claim before the side effect. If neither boundary is available, fail closed and do not automate the send. Before selecting the keyed sender lane, resolve current aliases again to the surviving canonical id; an alias disagreement, superseded id, or pending merge remains ineligible. - Record pending, sent, failed, and ambiguous outcomes against the canonical contact. A timeout or unknown post-send state remains ineligible until reconciled; do not retry it as a fresh first touch. - Keep campaign qualification and social-post evidence in campaign/source tables when useful, but reference the canonical contact id. - Keep send, acceptance, and reply events as audit ledgers. Ingest them idempotently with a stable provider event or delivery id, scoped by provider and account when needed, and upsert or deduplicate on that key while keeping the accepted event row immutable. A workflow id, run id, or workflow-derived table operation id does not deduplicate a provider redelivery that starts a new run. These ledgers inform the canonical row; they are not competing CRMs. - Never create another CRM table for a new outreach flow when the canonical contact table family can be extended, partitioned, or migrated. If a new persistent table is still required, report the candidates rejected and why, the new table's grain and key, its owner and consumers, whether it is canonical or supporting data, and its migration or retirement plan. ## Uploaded File To Downloadable Artifact Checklist Use this uploaded-file-to-downloadable-artifact checklist when the user asks to upload a document/file and receive a downloadable result: - use `:file` for a new browser or CLI upload, and `:resource` when the user can pick an existing workspace resource - declare an invocation input with `:type :file` for a new upload, or `:type :resource` when the user can pick an existing workspace resource - set `:accept` to the expected MIME types - treat uploaded values as resource refs, not inline bytes - call installed/public apps by outcome first when OCR or another common substep is needed - persist generated Markdown, text, JSON, DOCX, PDF, or binary output with `:persist {:type :blob ...}` - use `:load` only where a downstream function needs hydrated persisted content - return a Markdown viewer with a `breyta-resource` `:view :download` fence - smoke test with `breyta flows run --upload`, then inspect `breyta resources workflow list ` and `breyta resources read ` Full recipe: `breyta docs show guide-persisted-results-and-resources --section "Uploaded File To Downloadable Artifact Recipe"`. ## 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-07-27T11:46:30+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, install and configure the public app in the caller workspace first when setup, billing, connections, or saved activation inputs matter. Then choose the reuse surface by caller: | Caller | Preferred surface | | --- | --- | | Authored Breyta flow, one installed child | `flow/call-flow` with `:installation-id` | | Authored Breyta flow, many installed children | `:fanout` with `{:type :call-flow ... :installation-id ...}` | | Hosted or local MCP-capable agent, such as Claude, Codex, or Cursor | Workspace MCP `call_flow_tool` with `installation_id` | | External non-MCP client | Installed HTTP interface | | Manual smoke test or debug run | CLI/API `flows run --installation-id` | Do not hand off author draft/live endpoints as the normal consumer integration path. Default reuse loop: 1. Before source edits, direct provider setup, or template duplication, show a visible reuse checklist: needed outputs/functions; Discover searches tried; relevant paid apps found; and selected/rejected paid apps with reasons. 2. Find reusable apps in Discover with `breyta flows discover search`. Search relevant Discover public and paid installable apps by requested outcome, business function, and output modality before falling back to approved templates or direct provider connections. For multimodal app requests, check every needed function, including text-generation apps, image-generation apps, video-generation apps, and any relevant paid app. For an agent that creates ads, example searches include `ad generator`, `copywriting`, `text-generation`, `OpenAI image generation`, `image-generation`, and `social ad creative`. 3. For paid public apps, use Discover or `https://breyta.ai/apps/` for checkout, purchase, subscription, prepaid runs, or trial entry before relying on the CLI/API install path. 4. Create, configure, and enable an installation in the caller workspace. 5. Inspect installation readiness with `breyta flows installations get --pretty`. Stop before wrapper design if `runReadiness` reports a billing, entitlement, setup, or binding block. 6. Inspect the source flow's concurrency policy before manual run QA. If the installed app should allow multiple active manual runs, avoid `:singleton :on-new-version :supersede`; it intentionally replaces the earlier run and can be mistaken for a broken installed-run UI. 7. Inspect callable surfaces with `breyta flows installations interfaces `. 8. Smoke-test through the installation: `breyta flows interfaces call --installation-id --input '{"key":"value"}' --wait`. 9. For authored Breyta wrapper flows, use `flow/call-flow` or `:fanout` with `:installation-id`; for MCP-capable agents, use workspace MCP `call_flow_tool` with `installation_id`; for external non-MCP clients, use the installation-scoped HTTP endpoint: `/api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}`. Generic paid-app wrapper pattern: 1. Parent flow collects the user's inputs and any installed app ids. 2. Install/configure the relevant paid app in the caller workspace. 3. Check `breyta flows installations get --pretty` and `breyta flows installations interfaces ` before wrapper design. 4. Parent flow calls the installed app with `flow/call-flow` and `:installation-id`. 5. Parent flow transforms that output into the next app's input when another relevant paid app should be chained. 6. Parent flow returns a polished combined output and links any generated resources. Authored wrapper flows must use the positional `flow/call-flow` contract: ```clojure (flow/call-flow :child-flow {:plain "child input"}) (flow/call-flow :reviewed-ai-response-generator {:prompt prompt :intended-audience intended-audience} {:installation-id reviewed-ai-response-installation-id}) ``` The third argument is the child call options map. Put `:installation-id` there, not inside the child input map. The one-map form is not supported: `(flow/call-flow {:flow-slug ... :installation-id ... :input ...})`. 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, prefer the workspace MCP server when the agent already has a workspace-scoped MCP connection. Use `search_flow_tools`, `inspect_flow_tool`, `call_flow_tool`, then `get_run_status`. A direct installed Streamable HTTP MCP endpoint remains useful when a caller deliberately wants per-flow isolation. Treat paid-flow errors such as `billing_trial_ended` or a returned checkout action as billing or trial state, not as proof that the interface contract is broken. Complete the checkout action in the web app, then retry the installation or installed-interface call. Known-good text-generation reuse example after checkout/readiness: ```bash breyta flows discover search "reviewed ai response text generation" --limit 5 --pretty # If Discover or https://breyta.ai/apps/reviewed-ai-response-generator returns # checkout, trial, or subscription entry, complete that web step first. breyta flows installations create reviewed-ai-response-generator --name "Reviewed AI response" --pretty breyta flows installations get --pretty breyta flows installations interfaces --pretty breyta flows interfaces call reviewed-ai-response-generator generate-reviewed-response-api \ --installation-id \ --input '{"intended_audience":"founders evaluating workflow automation tools","use_case":"concise product explanation","task":"Write a two-sentence explanation of why reviewed AI output reduces editing time."}' \ --wait --pretty ``` Expected success shape after checkout/entitlement/setup is complete: the installation `get` response has `runReadiness.ready: true`; the interfaces response includes `generate-reviewed-response-api` with `target: "installation"`; the interface call returns `ok: true`, a completed run/workflow id, an `outputWebUrl`, and a Markdown response artifact. If `runReadiness.ready` is false, resolve the reported billing, entitlement, setup, or binding block before designing a wrapper flow. For image-generation reuse, run the same sequence with `breyta flows discover search "image generation"` and a matching image app such as `create-openai-2-image-http-mcp`. If the installed app page should support several user-started runs at once, verify that the source flow is not using `{:concurrency {:type :singleton :on-new-version :supersede}}`. That policy is appropriate for replacement semantics, but it will intentionally make the newer run replace the earlier in-flight run during manual QA. Known-good authored wrapper source shape: ```clojure '(let [input (flow/input) reviewed-ai-response-installation-id (or (:reviewed-ai-response-installation-id input) (get input "reviewed-ai-response-installation-id")) prompt (or (:prompt input) (get input "prompt")) reviewed (flow/call-flow :reviewed-ai-response-generator {:prompt prompt} {:installation-id reviewed-ai-response-installation-id})] {:reviewed reviewed}) ``` ## Using Another Creator's Paid App (Install -> Run -> Read Output) To use a paid/marketplace app published by another creator (not one you authored), run the installer flow end to end and read the output the way a normal user would. This is distinct from wrapping an app inside an authored flow above; use this when the CLI/agent itself is the consumer. 1. Find: `breyta flows discover search ""` (not `flows search`/`flows templates`, which are your own/workspace flows). 2. Install: `breyta flows installations create [--source-workspace-id --source-flow-slug ]`. 3. Check readiness: `breyta flows installations get --pretty` -> `runReadiness`. If `ready: true` (for example a no-card trial) run it. If blocked, the response carries a `checkoutPath`/`billingUrl`; a human completes that in the web app (agents cannot pay), then re-check. 4. Configure setup: `breyta flows installations configure --input '{...}' --bindings '{...}'` (activation fields and installer-owned connections in one call). An OAuth connection needs a one-time browser authorize first; the command consumes the resulting binding. Author-provided connections need no installer action. 5. Enable + run: `breyta flows installations enable `, then `breyta flows run --installation-id --input '{...}' [--upload =] --wait`. 6. Inspect + read output: `breyta runs inspect --installation-id `. Installed runs require `--installation-id` (it is in every `runs list` row and the run-start response); without it `runs inspect`/`runs events`/`runs step` return `Run not found` by design. Read the final output and any `res://...` artifacts with `breyta resources read `. Installer visibility: as the installer you get the run's status, timing, final output, and artifacts, the same view a person sees on the run page. The flow's internal steps belong to the creator and are not exposed to installers at all: `runs inspect`/`runs events`/`runs step --installation-id` return no step list, step previews, or step captures (the response marks `stepsRedacted`). So do not rely on reading another creator's step internals to verify an installed run; verify from the final output and artifacts. (When you are the creator inspecting your own flow's run in the source workspace, the full step captures are available to you.) ## 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, including `breyta flows public publish ` - 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 public visibility with both `:discover {:public true}` and `:marketplace {:visible true}` before `breyta flows push --file ...`, or run `breyta flows public publish ` after push and release. 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. 6. Bind installer-owned connections and setup inputs on the installation. 7. Run the installed target with `breyta flows run --installation-id --wait`. 8. Run the install test matrix in `playbooks/release-and-install.md` for each declared surface: optional integrations absent and broken, explicit-false toggles, malformed constrained text. 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 Screen First For public apps, design the installer's setup screen before the flow, then derive `:requires` from the approved screen: - at most one connection card per provider account and origin plus at most one required text field; reject any `:requires` list where two slots share a provider account and origin - one connection per provider account and origin; prefer a bare-origin `:base-url` with path prefixes in step `:path`, and never split slots by path prefix on the same origin. APIs a provider serves from distinct hosts need separate slots, and provider-backed steps that require a prefixed `:base-url`, such as self-managed GitLab `/api/v4`, keep their provider contract - prefer the built-in provider button when one covers the needed account and scopes; use a custom OAuth template only when no built-in provider covers it and the provider supports a confidential client (the custom OAuth form requires both client id and secret); for public-client/PKCE-only providers fall back to api-key or token paste when the provider offers one, and otherwise report the platform limitation instead of shipping an unusable slot - probe the exact auth shape live before shipping a slot: a `:bearer` slot bound to a bare api-key connection passes `configure check` and 403s at runtime for installers - derive, do not ask: when a connected API can enumerate a value, such as site properties, repos, default branches, or projects, resolve it at run start and demote any matching form field to an optional override - give each remaining free-text field a placeholder, a format-first description, run-start normalization, and validation errors that show a truncated preview of the attempted value; for enumerable inputs list a bounded sample of available options with a total count, and for genuinely free-form text return a format or limit error ## 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-run rows are readable - use viewer `:breyta.viewer/options {:title ...}` for viewer metadata or grouped child labels; do not rely on it to rename the canonical `flow-output` resource or single output chrome - treat run-list labels, viewer metadata, and persisted resource names as separate surfaces - prefer `:resource` fields for selecting existing artifacts, and keep manual upload available when useful ## Degradation Contract Installed runs degrade; they do not die. Decide the degraded shape of every optional integration before release: - optional-integration failure never kills the run: mark the step `:on-error :continue`, which yields `nil` on failure, then normalize that `nil` downstream to an explicit safe default (for example `(or (flow/step ...) {})` or a dedicated defaulting step) and add a report line saying what was skipped and why - distinguish "integration errored" from "returned no data": a failed optional step should surface as `skipped`, never as a silent zero that looks like a healthy `completed` result - never attribute one account's or site's data to another: no first-row fallback when an identifier fails to match; fail closed and list the available options - never claim an action the run did not take - failure copy names the app as the failing party; do not imply user error for app defects ## 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. - The publish description is stored flow metadata; author it as `:publish-description` in source and persist it on push, or set it directly with `--publish-description-file`. Update and release republish changed metadata for already-public flows; reserve `breyta flows public publish ` for visibility changes or a retry after a failed publish, and verify the public page after edits. - 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-06-15T10:08:01+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. - For public or paid installed apps with a manual run surface, choose concurrency based on the intended end-user UX, not only author iteration speed. `:singleton :supersede` is reasonable for hot iteration, webhook refresh jobs, and "latest run wins" automation. It is the wrong default when the installed app page is supposed to let users start another run while an earlier one is still visible or still working. - `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? ## Installed Manual-Run UX When debugging a public or paid app that appears to "lose" earlier runs from the installed app page, check the flow's `:concurrency` before assuming a UI or billing regression. - `:singleton :on-new-version :supersede` can terminate or replace an earlier active run when a new run starts. In manual QA this looks like "the second run trumped the first". - `:singleton :on-new-version :coexist` is usually the right policy when the installed app page should allow another run to start without replacing the earlier run card/state. - `flows run` / `flows interfaces call` plus `concurrencyDecision`, run timing, and the run list are the quickest proof of whether the backend intentionally superseded the earlier run. For reusable paid/public smoke fixtures, prefer `:coexist` unless the product semantics explicitly require replacement. ## 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 - use one async item mode per fanout: all `:call-flow` items or all named `:agent` items - 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-07-21T15:02:34+02:00 # CLI Workflow ## Quick Answer Default flow lifecycle: 1. Create or obtain a local source file (`init` for a new flow, `pull` for an existing flow) 2. Edit the local source (`steps`/`compose` helpers are optional) 3. Persist the working copy explicitly with `push` 4. Verify runtime (`run`) 5. 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 Compatibility: `breyta discover ...` is a newer shortcut for the discover surface. If your CLI does not show it in `breyta help`, use the released `breyta flows discover ...` form shown below. | 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` (add `--folder "Company information"` to store the file inside a named Storage folder) | | 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 ` | | Start a new local flow | `breyta flows init --name "My flow"` (creates `flows/.clj`; includes a no-input manual `run` interface and `:schedules []`) | | Add or replace a packaged step locally | `breyta flows steps create --step-file ./steps/step.edn`; `breyta flows steps update --step-file ./steps/step.edn` | | Add or update a local schedule | `breyta flows schedules add --cron "0 9 * * MON" --timezone UTC`; use `schedules update ... --schedule-file ./schedule.edn` for a complete map | | Compose the orchestration body | `breyta flows compose --body-file ./flows/.body.clj` (rewrites only quoted `:flow`) | | Prove a packaged step without persisting a draft | `breyta flows steps run --params ''` (sends the complete local literal for just-in-time execution) | | Pull local file | `breyta flows pull --out ./tmp/flows/.clj` | | Push working copy | `breyta flows push --file ./tmp/flows/.clj` (the explicit draft persistence step) | | 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"}'` | | Publish or delist all public surfaces | `breyta flows public publish `; `breyta flows public delist ` | | Update only marketplace visibility | `breyta flows marketplace update --visible=true` | | Update only 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; use `--upload field=path` for manual `:file`/`:blob-ref` inputs | | 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) For a new flow, initialize the local source first. The generated definition has one no-input manual interface and an empty `:schedules` vector, so a manual smoke run works immediately and later schedules stay in the same source file: ```bash breyta flows init order-sync --name "Order sync" breyta flows steps create order-sync tools/fetch-order --step-file ./steps/fetch-order.edn breyta flows schedules add order-sync daily-review --cron "0 9 * * MON" --timezone UTC breyta flows compose order-sync --body-file ./flows/order-sync.body.clj breyta flows lint --file ./flows/order-sync.clj --local-only breyta flows steps run order-sync tools/fetch-order --params '{"orderId":"order-123"}' breyta flows push --file ./flows/order-sync.clj ``` `flows steps create`, `update`, and `remove` edit only the top-level `:steps` vector; removal is blocked when the local `:flow` still references the step. `flows schedules add`, `update`, and `remove` edit only the top-level `:schedules` vector. Schedule `update` takes a complete schedule map so fields such as `:overlap-policy` are preserved. `flows compose` edits only the quoted top-level `:flow` form. This keeps packaged step definitions, interfaces, schedules, functions, providers, and connection metadata stable while the orchestration is composed. The step-run command sends the complete local literal to the server for an ephemeral, just-in-time execution; it does not create or update a draft. Use `flows push` when the local source should become the workspace draft. The `--push` flag on `init`, step CRUD, schedule CRUD, and `compose` is an explicit convenience for the same action. Local lint catches missing qualified packaged-step references; server lint/push remains the canonical schema check. For an existing remote flow, pull it first, then use the same local loop: 1. `breyta flows pull --out ./tmp/flows/.clj` 2. Edit the file, or use `flows steps ...` / `flows compose` 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. ## CLI Manual Upload Smoke Tests For invocation inputs typed `:file`, `:blob`, or `:blob-ref`, prefer `flows run --upload field=path` when you need the CLI to emulate a browser/manual upload. The CLI uploads the local file as a workspace file resource, then sends that input field as a canonical `resource-ref` for the run: ```bash breyta flows run thesis-pdf-review-docx \ --target draft \ --interface-id run \ --upload thesis=./thesis.pdf \ --wait ``` Combine `--upload` with `--input` for the non-file fields: ```bash breyta flows run thesis-pdf-review-docx \ --target draft \ --interface-id run \ --input '{"reviewDepth":"smoke"}' \ --upload thesis=./thesis.pdf \ --wait ``` Do not put the same field in both `--input` and `--upload`; the CLI rejects that ambiguity. Repeat `--upload` for multi-file inputs. When the flow should return a downloadable artifact, persist the generated file as a blob and render it from the final Markdown output with a `breyta-resource` `:view :download` fence. See [Persisted Results And Resources](/docs/guide-persisted-results-and-resources#uploaded-file-to-downloadable-artifact-recipe) for the complete upload-to-download recipe. 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. - Use `breyta flows public publish ` or `breyta flows public delist ` when a flow should move onto or off all public surfaces together: marketplace visibility, Discover listing, and the public app page. - Marketplace visibility is stored metadata. Use `breyta flows marketplace update --visible=true|false` only when you intentionally need to change that lower-level flag without changing Discover visibility. - 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|false` only when you intentionally need to change the lower-level Discover flag without changing marketplace visibility. 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 public visibility path: - Source-first: add both `:marketplace {:visible true}` and `:discover {:public true}` before push only after explicit approval. - Explicit after release: run `breyta flows public publish ` after push and release, only after explicit approval. 4. Push the flow with `breyta flows push --file ...`. 5. Release/promote it so there is an installable live version. 6. If you chose the explicit public path, run `breyta flows public publish ` after release. Use `breyta flows public delist ` to remove the flow from all public surfaces later. 7. 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 your installations for one flow | `breyta flows installations list ` | | List current-workspace installed-app inventory | `breyta flows installations list --all` | | Filter current-workspace inventory to one flow | `breyta flows installations list --all` | | List every consumer user's installation (flow creator only) | `breyta flows installations list --all-users` | | 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`, and public-app `runReadiness` when billing/trial entitlement is known) | | 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` - Buyer Test Mode author run, after creating the Buyer Test source install: `breyta flows run --buyer-test --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` | | Buyer Test installation | `breyta flows run --buyer-test --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, and one webhook adapter. It may expose multiple MCP tools when the same flow intentionally offers several agent-callable operations, but use extra invocation ids only when a tool needs a genuinely different payload contract, not to turn one flow into several unrelated apps. 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. For paid public apps, start from Discover or `https://breyta.ai/apps/` when checkout, purchase, subscription, prepaid runs, or trial entry is required. Complete that web entitlement step first. 3. Create, configure, and enable an installation in the caller workspace. 4. Inspect `breyta flows installations interfaces `. 5. Inspect `breyta flows installations get ` and check `data.runReadiness` before building wrappers around paid public apps. 6. Choose the call surface by caller: `flow/call-flow` with `:installation-id` inside authored Breyta flows, workspace MCP `call_flow_tool` with `installation_id` from MCP-capable agents, or the installation-scoped HTTP interface from external non-MCP clients. 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-capable agents, prefer the workspace MCP server path: `search_flow_tools`, `inspect_flow_tool`, `call_flow_tool`, then `get_run_status`. Pass `installation_id` when targeting a specific installed app instance. A direct installed Streamable HTTP MCP endpoint is still available when the caller deliberately wants per-flow isolation. Paid public flows can return entitlement or checkout errors such as `billing_trial_ended`; handle those as billing state rather than interface-shape failures. Complete the returned checkout action in the web app, then retry the install or interface call. `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. | | Run a paired Buyer Test installation | `--buyer-test --installation-id ` | Makes the Buyer Test Mode install-run intent explicit after the paired Buyer Test source install exists. The server derives Buyer Test behavior from the current workspace and installation; do not combine it with `--target`. | | 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. | When a run starts and the flow has prior run history, the start response includes `avgDurationMs` (the historical average completed runtime, in milliseconds), and the CLI prints an estimate such as `Runs of this flow usually finish in about 2 minutes.` to stderr before polling. Use it to gauge how long a `--wait` run will take instead of waiting blindly. The estimate is omitted for flows with no completed run history. ## 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/playbook-author-flows) - [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-07-23T14:31:02+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 public publish ``` Requirements: - public visibility must be enabled for marketplace and Discover surfaces - 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 both `:discover {:public true}` and `:marketplace {:visible true}` before push only after explicit approval. - Explicit after release: run `breyta flows public publish ` after push and release, only after explicit approval. 2. Push the flow with `breyta flows push --file ...`. 3. Release/promote it so there is an installable live version. 4. If you chose the explicit path, run `breyta flows public publish ` after release. 5. Verify the web Discover install dialog. For CLI indexing checks, use another workspace or `--include-own` only while debugging the owner workspace. 6. For paid reusable apps, keep Discover/search metadata current before relying on template-adjacent reuse suggestions: tags should name the output, business function, modality, paid/installable status, and callable surface; then run the normal search reindex/backfill operation for the touched app. 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 app requests, show a visible reuse checklist before source edits, direct provider setup, or template duplication: needed outputs/functions; Discover searches tried; relevant paid apps found; and selected/rejected paid apps with reasons. Search relevant Discover public and paid installable apps by requested outcome, business function, and output modality. For multimodal apps, check every needed function. For example, check text-generation apps, image-generation apps, video-generation apps, and any relevant paid app separately before designing a wrapper flow. For an agent that creates ads, example searches include `ad generator`, `copywriting`, `text-generation`, `OpenAI image generation`, `image-generation`, and `social ad creative`. 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, inspect run readiness, and run the installed target: ```bash breyta flows installations create --name "Smoke install" breyta flows installations configure --input '{"":""}' breyta flows installations enable breyta flows installations get --pretty breyta flows run --installation-id --wait ``` When `` comes from `breyta flows discover list` or `breyta flows discover search `, the CLI resolves a unique public Discover match from the source workspace automatically. If the slug is ambiguous across public sources, pass the Discover source fields explicitly: ```bash breyta flows installations create --source-workspace-id --source-flow-slug ``` When a public app entry point supplies a `publicAppFunnelId`, preserve that attribution on the installation with `--public-app-funnel-id`. The API stores the value on the installation source reference and includes it in any checkout action returned by installation creation: ```bash breyta flows installations create \ --source-workspace-id \ --source-flow-slug \ --public-app-funnel-id ``` Set attribution when creating the installation. Later `configure`, `enable`, and interface commands reuse the stored source reference, so they do not accept a second funnel-id flag. Authors testing their own paid/public source app through Buyer Test Mode should run the install command from the Buyer Test workspace and make the source install intent explicit. `--buyer-test` is an installation-scoped CLI intent guard for the run command; the server derives Buyer Test behavior from the current workspace and installation, so do not combine it with `--target`. ```bash breyta flows installations create \ --buyer-test-source-install \ --source-workspace-id \ --source-flow-slug breyta flows run --buyer-test --installation-id --wait ``` For paid public apps, the Discover/public app CTA owns the payment or trial entry step. A before-install paywall routes through checkout first, then opens the installation setup page for the created installation: ```text //flows//installations/?configure=setup ``` After setup, call the installed app from its installation page, with `breyta flows interfaces call ... --installation-id `, or with the installed interface API endpoint. If the CLI/API returns an entitlement or checkout action, complete that web checkout before retrying the install or run command. Generic paid-app wrapper pattern: 1. Parent flow collects the user's inputs and any installed app ids. 2. Install/configure the relevant paid app in the caller workspace. 3. Check `breyta flows installations get --pretty` and `breyta flows installations interfaces ` before wrapper design. 4. Parent flow calls the installed app with `flow/call-flow` and `:installation-id`. 5. Parent flow transforms that output into the next app's input when another relevant paid app should be chained. 6. Parent flow returns a polished combined output and links any generated resources. Public app reuse example: ```bash breyta flows discover search "reviewed ai response text generation" --limit 5 --pretty # If Discover or https://breyta.ai/apps/reviewed-ai-response-generator returns # checkout, trial, or subscription entry, complete that web step first. breyta flows installations create reviewed-ai-response-generator --name "Reviewed AI response" --pretty breyta flows installations get --pretty breyta flows installations interfaces --pretty breyta flows interfaces call reviewed-ai-response-generator generate-reviewed-response-api \ --installation-id \ --input '{"intended_audience":"founders evaluating workflow automation tools","use_case":"concise product explanation","task":"Write a two-sentence explanation of why reviewed AI output reduces editing time."}' \ --wait --pretty ``` Expected success shape after checkout/entitlement/setup is complete: `runReadiness.ready` is true before the call, the interfaces list includes an installation-targeted HTTP or MCP interface, and the call returns `ok: true`, a terminal completed run/workflow id, and an `outputWebUrl` with the generated result. If readiness reports `billing_subscription_required`, `billing_trial_ended`, `invalid_flow_configuration`, an entitlement block, or missing bindings, fix that first instead of building a wrapper around a blocked installation. 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. Authors do not need to authorize their own provider account to create the template; use `Save for installers` after entering provider metadata and client credentials. Use `Save and connect` only when the author also wants a test connection in the author workspace. 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. Custom OAuth account switching is also provider-specific; add the provider's documented account chooser prompt to the template's extra authorize params when required. 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. - When `:title` resolves to a value, the recent-run row uses that title with the run timestamp. It does not append fallback resource-count copy. - If `:title` is not set, completed recent-run rows keep the fallback title such as `1 resource created`. - `:title` is only for the run list. Viewer envelopes can include `:breyta.viewer/options {:title ...}` for viewer metadata and grouped item labels, but a single final output page or panel keeps platform chrome such as `Final output`. The canonical `flow-output` resource name is platform-managed. - Keep run titles, viewer titles, and persisted resource names as separate naming surfaces. 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 ` | | 5. Run the installed app | `breyta flows run --installation-id --input '{...}' --wait` | | 6. List your runs (each row includes its `installationId`) | `breyta runs list --installation-id ` | | 7. Inspect a run and read its output | `breyta runs inspect --installation-id ` | This is the end-to-end path for using an app published by **another** creator, not just one you authored yourself: discover (`breyta flows discover search`), install, configure, enable, run, then inspect and read the output. Pass `--installation-id` when inspecting an installed run. `breyta runs inspect`, `breyta runs events`, and `breyta runs step` resolve an installed run only when the installation is supplied; without it they return `Run not found` (404) by design, because a run can map to more than one installation. The `installationId` is in every `runs list` row and in the `runs.start`/run response, so pass it back to `runs inspect`. As the installer you see your run's **status, timing, final output, and artifacts** — the same view a person gets on the run page in the web app. The flow's internal steps are the creator's and are not exposed to installers at all: `runs inspect`/`runs events`/`runs step` return no step list, step previews, or step captures for an installed run (the response marks `stepsRedacted`). Read the final output and any generated artifacts with `breyta resources read ` (artifact `res://...` refs appear in the run's output). 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 Install the public flow first when it has installer-owned setup, billing, connections, or saved activation inputs. After install, choose the call surface by caller: | Caller | Use | Notes | | --- | --- | --- | | Authored Breyta flow, one installed child | `flow/call-flow` with `:installation-id` | Native child-flow composition. | | Authored Breyta flow, many installed children | `:fanout` with `{:type :call-flow ... :installation-id ...}` | Bounded parallel child-flow spawn/collect. | | Claude, Codex, Cursor, or another MCP-capable agent | Workspace MCP `search_flow_tools`, `inspect_flow_tool`, `call_flow_tool`, then `get_run_status` | Pass `installation_id` when targeting a specific installed app instance. | | External non-MCP system | Installed HTTP interface | Use the installation-scoped endpoint and bearer auth. | | Manual smoke test or debug run | CLI/API `flows run --installation-id` | Control-plane/debug path, not the default wrapper-flow composition path. | `flow/call-flow` is the Breyta flow DSL child composition form. `call_flow_tool` is the workspace MCP broker tool for external MCP clients. Do not make a wrapper flow call its installed child through the HTTP interface unless you are intentionally testing the external HTTP transport; the native wrapper-flow path is `flow/call-flow` with `:installation-id`. Typical install 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. Inspect runtime readiness before composing a wrapper around the install: ```bash breyta flows installations get ``` For paid public apps, the response includes `data.runReadiness`. It mirrors the same gate the run itself uses, so an app with free trial runs available reports `runReadiness.ready: true` and can be run without any billing step. A blocked value, such as `public_flow_trial_runs_exhausted` (free trial used up) or a card-upfront paid app, sets `runReadiness.ready: false` and returns a `billingUrl`/`checkoutPath`; complete that checkout action before wrapper runs, HTTP calls, or MCP calls can succeed. A `reason` of `installation_unavailable` (no billing block) means the source app is currently unavailable (for example disabled or unpublished), not a billing issue. A `reason` of `installation_setup_incomplete` means the app is available but still needs installer-owned bindings or required setup fields; inspect `runReadiness.setupState`, complete those items, and then enable the installation. A `reason` of `invalid_flow_configuration` means the app has an authored step-configuration bug that would fail every run (for example a step value over its allowed range); billing and setup cannot fix it, so do not retry or build a wrapper — contact the app owner to fix the app. If manual QA from the installed app page appears to "lose" earlier runs when a new run starts, inspect the source flow's `:concurrency` before assuming the installed UI is broken. `{:type :singleton :on-new-version :supersede}` intentionally replaces the earlier in-flight run. Use `:coexist` when the installed app is supposed to allow multiple user-started runs without replacing the earlier one. 5. Smoke-test the installed interface: ```bash breyta flows interfaces call \ --installation-id \ --input '{"prompt":"A clean product image","quality":"low"}' \ --wait ``` Inside an authored Breyta wrapper flow, call the installed flow as a child: ```clojure '(let [input (flow/input) reviewed (flow/call-flow :reviewed-ai-response-generator {:prompt (:prompt input)} {:installation-id "installation-id-from-setup"})] {:reviewed reviewed}) ``` `flow/call-flow` uses positional arguments: child flow slug, child input map, then optional child call options. Put `:installation-id` in the third map. Do not use a one-map shape like `(flow/call-flow {:flow-slug ... :installation-id ... :input ...})`. For a bounded batch of installed child runs, use `:fanout` with `:call-flow` items: ```clojure (flow/step :fanout :review-prompts {:items (mapv (fn [prompt] {:type :call-flow :flow-slug :reviewed-ai-response-generator :installation-id "installation-id-from-setup" :input {:prompt prompt}}) prompts) :max-concurrency 5 :on-error :collect}) ``` For external non-MCP clients, use the installed HTTP endpoint: ```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 can either use the workspace MCP server or a direct installed Streamable HTTP MCP endpoint. Prefer the workspace MCP server for Claude, Codex, Cursor, and similar agents because one workspace server can discover and call accessible installed flow tools: ```json { "flow_slug": "reviewed-ai-response-generator", "tool_name": "generate_reviewed_ai_response", "installation_id": "", "input": {"prompt": "Draft a short response"} } ``` A successful `call_flow_tool` starts the backing flow run and returns run metadata for `get_run_status`. Paid public flows can reject installed interface calls when entitlement or trial runs are exhausted. Treat billing errors such as `billing_trial_ended` or `public_flow_trial_runs_exhausted` as entitlement state, not as evidence that the wrapper, HTTP, or MCP interface is broken. Use `breyta flows installations get ` to preflight readiness before building or smoke-testing wrappers around a paid public app. 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. ### Verify an installed-app run, and never blind-retry or cancel it When you start an installed-app run from an agent or the CLI, the run is yours as the installer. For a paid or trial app it reserves one unit of your usage, it runs in your workspace context, and its final output is yours to read. The execution itself stays owned and operated by the app's creator, so an installed run is opaque to you: you get status, timing, and final output, never the internal steps. Treat the `workflowId` from the start response as the run handle, and reconcile with it instead of re-running: | Goal | Command | |---|---| | Confirm the run exists and see its status | `breyta runs list --installation-id ` | | Read one run's status, timing, and output | `breyta runs inspect --installation-id ` | The `--query installation:` form of `runs list` works too. Always pass `--installation-id` to `runs inspect` for an installed run; without it the lookup returns `Run not found` (404) by design, because a run can map to more than one installation. **A start request that returns a transient server error (for example a 502, a 500, or a gateway timeout) is not proof the run failed.** The run is created before its usage is reserved, so a transient error after the run started can still leave a real run in your history. Do not blindly retry a side-effectful installed run on a transient error: a blind retry can start a second run, and if both runs complete you are charged for both. Instead: 1. List runs for the installation with `runs list --installation-id `. 2. If a matching run is present, take its `workflowId` and `runs inspect` it. The run either already completed (read its output) or is still going (wait for it). 3. Start a new run only if no run was created. **Do not cancel an installed-app run.** `runs cancel` is meant for the flow's creator or operator, not for installers, and cancelling an installed run is not part of the install-and-run loop. (A cancel of an installed run now returns a clean `cancelled` result rather than a spurious server error, but the run still executes in the creator's runtime workspace, so cancellation is the creator's or operator's action, not the installer's.) You do not need cancel for billing either: a run consumes your paid or trial allowance only when it completes. A run that ends any other way (cancelled, failed, or timed out) releases its reservation and is not charged. So a run that errors out or never finishes does not cost you anything; verify its final status with `runs inspect --installation-id ` instead of trying to cancel it. ### 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 :coexist} :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 - `:on-new-version :coexist` keeps installed manual runs from replacing each other during normal user operation or QA; use `:supersede` only when replacement semantics are intentional - 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. Use `:workspace-flows` when a setup or run field should target another flow in the installer workspace: ```clojure {:key :target-flow :label "Flow to update" :field-type :select :required true :options-source {:type :workspace-flows}} ``` The submitted value defaults to the selected flow slug. Authors can use `:value-field`, `:label-field`, and `:description-field` to map another field from the flow metadata, but the default slug/name/description mapping is the normal choice. The picker stores a normal form value. To attach a generated cover image to the selected flow, call `:breyta` with `:op :flows/update`, allow that operation, and set `:mutations :write`. Use `:publish-media {:type :image :source {:kind :flow-resource :uri }}` when the image was persisted by the run. 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-07-23T12:09:41+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, prepaid usage pack, 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`, `Prepaid run pack`, or `Subscription usage`. 4. Enter amount and currency. 5. For subscriptions, choose the billing interval and optional trial settings. 6. For prepaid usage packs, enter the run 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 run pack | A fixed number of successful runs with no time expiry | "$99 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` | Pricing guidance: - Prefer subscriptions, subscription usage, and prepaid usage packs for paid marketplace apps. - Avoid tiny payments and per-run microtransactions. If the app is mainly for sampling or lead generation, use free access or trial runs instead. - Price from buyer value first, then check costs and margins with run cost estimates, configured step `:metering`, external API or LLM costs, payment fees, Breyta platform fees, and expected support burden. - Breyta apps can have low operational overhead, so the cost floor may be low; that does not mean every app should be priced at the floor. - Good starting shapes are `$49/mo` with included runs, `$99` prepaid packs, or meaningful fixed-fee purchases such as `$199` diagnostics. 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 `"49.50"` USD. Marketplace pricing guidance still discourages tiny checkout amounts. - `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. For app-owned plan catalogs, put `:paywall {:when ...}` on each plan when timing is plan-specific; a root `:paywall` under `:monetization` remains supported as a shared default. - `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. - `plans[].visible`: set to `false` to keep an existing plan stored but hide it from new public plan selection. - `plans[].retired`: set to `true` to keep an existing plan stored but remove it from new public plan selection. Use this for obsolete plan IDs that historical checkout, entitlement, billing, or audit records may still reference. - 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"} :paywall {:when :before-install} :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} :paywall {:when :before-install}} {:plan-id "metered" :name "Metered" :pricing {:type "subscription-usage" :amount 99 :currency "usd" :interval "month" :unit "run" :included-quantity 1000} :paywall {:when :before-install}}]}}}} ``` 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 - Breyta does not infer a billing plan from runtime form input values. A `:before-run` app with multiple plans uses the selected checkout/install plan, or the default visible plan when no explicit plan is supplied. If a run form value should determine the charged tier, model that as separate public plans with an explicit plan selection surface; do not rely on matching a field value such as `"deep"` to a `plan-id` of `"deep"`. 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 public visibility path: - source-first: add both `:marketplace {:visible true}` and `:discover {:public true}` in the flow file before push - explicit after release: run `breyta flows public publish ` 4. Push the flow: ```bash breyta flows push --file ./tmp/flows/.clj ``` 5. Release a live version: ```bash breyta flows release --release-note-file ./release-note.md ``` 6. Run `breyta flows public publish ` if you chose the explicit path. 7. Verify the buyer surface from another workspace. ## Buyer Install And Invocation Path Use the web Discover/public app surface for the paid entitlement step. The supported buyer entry points are: - `//discover` - `https://breyta.ai/apps/` For a normal free public app, the install CTA creates the installation and then opens installation setup. For a paid public app, the CTA first reflects the paid state: | Paid state | CTA | What happens before install | |---|---|---| | subscription or subscription usage | `Subscribe` | Stripe Checkout starts the subscription or confirms the trial | | one-time purchase | `Purchase` | Stripe Checkout completes the purchase | | prepaid usage pack | `Buy runs` | Stripe Checkout buys the run pack | | no-card trial | `Start trial` | Breyta creates the trial entitlement without charging a card | When the paid flow uses a `:before-install` paywall, Breyta must have an entitlement before it creates the installation. After checkout or trial entry, Breyta opens the installation setup surface: ```text //flows//installations/?configure=setup ``` The buyer completes required setup inputs and connection bindings there, then opens the installed app page to run it: ```text //flows//installations/ ``` If the flow uses a `:before-run` paywall, installation can exist before payment, but run and installed-interface calls are blocked until checkout or entitlement recovery completes. CLI installation support starts after the paid entitlement exists, or for free public apps that do not need entitlement setup. If a paid app offers a no-card trial, start it from Discover or the public app checkout action first, then use the CLI to create/configure/enable the installation and to call installed interfaces: ```bash breyta flows installations create --name "Client install" 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 ``` If the public app entry point supplies a `publicAppFunnelId`, add `--public-app-funnel-id ` to `flows installations create`. Breyta stores the attribution on the installation and carries it into any checkout action returned by the create command. Configuration and later installation commands reuse the stored attribution. If a CLI command hits a missing or expired paid entitlement, treat the returned checkout action as the recovery path. Complete that web checkout, then rerun the installation or interface command. ## Buyer Payment Method Reuse Paid public-app Checkout stores one shared marketplace Stripe Customer on the buyer workspace at `billing/state.marketplace.stripeCustomerId`. Later paid app Checkout Sessions for the same buyer workspace pass that Customer to Stripe, so Stripe can redisplay eligible saved payment methods across paid Breyta apps. Breyta writes this shared Customer from both completion paths: - the browser return path after Checkout completes - the Stripe `checkout.session.completed` webhook path after the paid entitlement leaf is written Entitlement leaves still keep their own `customerId` for audit and recovery. If an older paid entitlement exists but the shared marketplace Customer is missing, the next paid-app Checkout attempts a lazy repair from the newest completed paid entitlement before creating the Stripe Checkout Session. Stripe only redisplays payment methods that are eligible for redisplay. In practice, the buyer must have saved the method during Checkout or otherwise made it reusable. If the card was not saved or is not redisplay-eligible, Checkout can still ask for payment details even when Breyta passes the shared Customer. Programmatic callers use the installed-interface endpoint, not the source flow's normal draft/live run URL: ```text /api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id} /api/workspaces/{buyer-workspace-id}/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id} ``` MCP-capable agents normally use the workspace MCP server: `search_flow_tools`, `inspect_flow_tool`, `call_flow_tool` with `installation_id`, then `get_run_status`. A direct installed Streamable HTTP MCP endpoint remains available when the caller deliberately wants per-flow isolation. ## 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) - [Marketplace App Pricing](/docs/guide-marketplace-app-pricing) - [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-07-30T18:15:38+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. Except for documented control sentinels such as `:generate`, Breyta treats each submitted `.secret` value as new plaintext. It stores the value under the slot's declared `:secret-ref`, replacing any value already stored there. A secret ID or `:secret-ref` name is not a reuse instruction. Passing one as the value stores that literal text as the new secret. For automation or explicit CLI setup, set the secret on the draft target: ```bash breyta flows configure --set webhook-secret.secret="YOUR_SECRET_VALUE" ``` To preserve an already-bound target's current secret value, omit the `.secret` field entirely. Omission does not bind an unconfigured draft target, even when the workspace secret store already contains the declared `:secret-ref`. Configure only the other required values, then run `breyta flows configure check `. Do not pass the secret ID as the 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 | | Create or replace value | `breyta flows configure --set .secret="VALUE"` | Stores the submitted plaintext under the slot's `:secret-ref`, replacing the previous value | | Preserve already-bound value | Omit `.secret` | Preserves the target's existing binding and value; does not bind an unconfigured draft target | | 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. Configure check reports secret slots alongside connection slots: `requiredSecretSlots`, `configuredSecretSlots`, and `missingSecretSlots`. Required secret slots keep `ready` false until the target has a secret binding or the declared secret ref passes the current presence check. Optional secret slots (`:optional true` or `:required false`) do not block readiness. A configured slot proves only that configuration metadata is present. It does not prove that a referenced secret still exists or that its value is valid. Run a provider-specific, read-only smoke test to prove the credential works. ### Author-provided secrets in installable apps For a public or installable app, use `:provided-by :author` for a platform credential that the app owner supplies once. Breyta resolves it from the author workspace at runtime and does not show it in installer setup. Keep each customer's account connection, including OAuth, `:provided-by :installer`. For example, a Google Ads app can combine installer OAuth with an author-owned developer token: ```clojure {:requires [{:slot :google-ads :type :http-api :provided-by :installer :base-url "https://googleads.googleapis.com" :oauth {:provider :google :scopes {:required ["https://www.googleapis.com/auth/adwords"]} :requires-refresh-token true}} {:slot :google-ads-developer-token :type :secret :secret-ref :google-ads-developer-token :provided-by :author}]} ``` The HTTP step uses the installer connection for OAuth and resolves the author-owned token in a separate header: ```clojure {:connection :google-ads :headers {"developer-token" {:secret-ref :google-ads-developer-token}}} ``` Configure the author-owned secret on the author's live target before release. Then verify a fresh installation asks for the customer OAuth connection but does not display the author-owned secret. ### 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. | | Passing a secret ID or `:secret-ref` name to `.secret` | Replaces the real value with that literal text while the slot can still appear configured | | 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-07-27T11:49:53+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 [Webhooks And Secret Refs](/docs/guide-webhooks-and-secret-refs) (`breyta docs show guide-webhooks-and-secret-refs`). 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 ...}`. For webhook interface setup, see [Webhooks And Secret Refs](/docs/guide-webhooks-and-secret-refs) (`breyta docs show guide-webhooks-and-secret-refs`). | | 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. (let [acme (flow/step :http :acme {:url "https://api.acme.com" ;; placeholder :method :post :json {}})] acme) ``` ## 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 --target draft --input '{}' --wait` - Brand-new imports have no active release yet, so plain `flows run` fails with `no_active_version`; `--target draft` runs the draft explicitly. Drop it once the flow is released. 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` uses the active release by default; brand-new unreleased flows need `--target draft` (as in step 7) or a release first. - `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-07-24T14:26:32+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. The canonical `flow-output` resource name is managed by Breyta and should not be used as the user-facing artifact title. Viewer envelopes can include `:title` in `:breyta.viewer/options` for viewer metadata and grouped item labels, but a single final output page or panel keeps platform chrome such as `Final output`. To name rows in an installed flow's recent-run list, set `:title` on the run form instead. Keep these naming surfaces separate: run-list rows use run form `:title`, viewer metadata uses `:breyta.viewer/options`, and persisted resources keep their platform-managed names. ## Where users see output For each run, output can be accessed from these user-facing surfaces: | Surface | Purpose | |---|---| | Run page sidepeek (`Artifacts`) | Primary, quick in-context output inspection. | | Sidepeek deep-link (`/:workspace-id/runs/:flow-slug/:run-id?output=panel`) | Shareable link that opens the run output inside the sidepeek panel in context. CLI-returned `outputWebUrl` uses this shape so shared output links stay in the app; `?output=fullscreen` opens the same panel expanded. | | Output route (`/:workspace-id/runs/:flow-slug/:run-id/output`) | Standalone full-page output view. | 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."} ``` Use the viewer title as metadata or as a label for grouped child artifacts: ```clojure {:breyta.viewer/kind :markdown :breyta.viewer/options {:title (str "Answer: " question)} :breyta.viewer/value markdown-report} ``` 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](/docs/reference-output-artifacts). ````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"} ``` For the full upload-to-download flow pattern, including `:file` or `:resource` inputs, generated blob persistence, final Markdown download embeds, and `flows run --upload` proof, see the [Uploaded File To Downloadable Artifact Recipe](/docs/guide-persisted-results-and-resources#uploaded-file-to-downloadable-artifact-recipe). ### 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](/docs/reference-output-artifacts) - [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-07-21T14:29:09+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. | | Use an unqualified `flow/call-flow` from a draft parent for same-workspace author-owned children that need profile-bound requirements. | Child preparation selects the enabled draft profile for the child flow without hardcoding a profile id. | | 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`. | | Treat a draft profile id as a child-flow target, not a parent-flow target. | Profile ids are flow-specific; installed public flows need the consumer `:installation-id`. | | Use `:fanout` only for bounded child-workflow batches (`:call-flow` items or named `:agent` items). | Keeps concurrency enforceable and avoids reopening generic parallel side effects. | | Let an orchestration agent use `:tools {:fanout {:agents [...]}}` for dynamic specialist fanout. | Keeps agent choice bounded before starting child workflows. See [Orchestration Agent Fanout](/docs/guide-orchestration-agent-fanout). | | 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` for success and terminal provider outcomes, then branch on the result; use `:abort-on` for API/transport errors. | Stops polling a finished actor while keeping provider failures from looking like successful flow runs. | | Prefer `:backoff` over manual interval math. | Centralizes retry interval control. | | Set `:id` for sleep step stability. | Improves trace/test consistency. | | Treat provider `maxPosts`/`limit` fields as provider hints, not Breyta cost caps. | Provider actors may paginate, fan out, or emit more events than the requested item count. | | Probe external actors with a small query and item budget before scaling target counts. | Reveals actual provider event/cost behavior before a large run. | | Cancel the remote actor separately when a poll timeout must stop provider work. | A Breyta polling timeout stops status checks; it does not necessarily stop the remote actor. | | Report requested target count, provider candidate pool, evaluated candidates, and accepted results separately. | A target count is often an output goal, not a guarantee after caps, filtering, deduplication, and provider availability. | | Preserve explicit ICP and role keywords, or expose the effective inferred terms and an override. | Keyword inference must not silently remove high-signal intent such as owner or decision-maker titles. | ## 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-07-21T14:29:21+02: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 "..."}}) ``` The same contract applies to an isolated CLI probe. Select the flow and source so the server can resolve the flow-local template, put interpolation values in `data`, and raise the client request bound when a provider call is slow: ```json {"connection":"ai","template":"findings","data":{"currentDate":"2026-01-16","text":"..."}} ``` ```bash breyta steps run --flow my-flow --source draft --type llm --id summarize \ --profile-id --params-file ./tmp/summarize.params.json --timeout 10m ``` Use the profile context when `ai` is a connection slot; alternatively replace `ai` with a direct connection ID that does not require profile bindings. `template` is the ID from the selected flow's `:templates` vector, not an approved catalog/template slug. `data` is the map used to render `{{...}}` variables; passing only the template ID does not supply those values. ### 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-07-31T11:58:19+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) | | Workspace MCP | Streamable HTTP workspace endpoint, setup checklist, scope bundles, toolsets, flow-tool discovery, source-definition resources, prompts, and feedback | [Workspace MCP](/docs/mcp-workspace) | | MCP client setup | Agentic coding tool setup for Codex, Claude Code, Cursor, Windsurf, VS Code Copilot, Cline, Roo Code, Continue, Gemini CLI, opencode, Goose, and Zed | [Connect Breyta MCP To Agentic Coding Tools](/docs/mcp-client-setup) | | MCP flow authoring | Source pull, draft creation, validation, and approval-gated release through MCP resources, prompts, `get_flow_definition`, `validate_flow_definition`, `create_flow_draft`, and `release_flow` | [MCP Flow Authoring](/docs/mcp-flow-authoring) | | MCP tool safety | MCP service-account capabilities, exposure headers, secrets, error handling, and feedback boundaries | [MCP Tool Safety](/docs/mcp-tool-safety) | | 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) | | Marketing Hub | Agent-built workspace tabs, governed cards, revisions, latest outputs, and suggestions | [Marketing Hub](/docs/guide-marketing-hub) | | Docs search | `breyta docs find`, query syntax, field filters, snippets, and explain mode | [Docs Search](/docs/reference-docs-search) | | 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](/docs/reference-flow-health) | | Service accounts | Workspace machine principals, API keys, and worker auth | [Service Accounts](/docs/reference-service-accounts) | | 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`, `:mcp`, `: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) | | Orchestration-agent fanout | Coordinator agent -> `fanout_agents` tool -> named specialist agents -> synthesis | [Orchestration Agent Fanout](/docs/guide-orchestration-agent-fanout) | | 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, called deterministically with `flow/step :mcp`, or selected by agents with `:tools {:mcp [...]}` | [Step MCP](/docs/reference-step-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/GitLab as built-in source import providers | [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`, `:json`, `: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](/docs/reference-table-resources) 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](/docs/reference-output-artifacts) | ## Navigation Hubs - [Start Here](/docs/start-here) - [Workspace MCP](/docs/mcp-workspace) - [Connect Breyta MCP To Agentic Coding Tools](/docs/mcp-client-setup) - [MCP Flow Authoring](/docs/mcp-flow-authoring) - [MCP Tool Safety](/docs/mcp-tool-safety) - [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) - [Orchestration Agent Fanout](/docs/guide-orchestration-agent-fanout) - [CLI Workflow](/docs/guide-cli-workflow) - [CLI Commands](/docs/reference-cli-commands) - [Marketing Hub](/docs/guide-marketing-hub) - [Docs Search](/docs/reference-docs-search) - [Flow Metadata And Discover](/docs/reference-flow-metadata) - [Paid Public Flows](/docs/guide-paid-public-flows) - [Flow Health](/docs/reference-flow-health) - [Service Accounts](/docs/reference-service-accounts) - [Table Resources](/docs/reference-table-resources) - [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](/docs/reference-output-artifacts) - [Troubleshooting](/docs/guide-troubleshooting) ## Related - [Start Here](/docs/start-here) - [Workspace MCP](/docs/mcp-workspace) - [Connect Breyta MCP To Agentic Coding Tools](/docs/mcp-client-setup) - [MCP Flow Authoring](/docs/mcp-flow-authoring) - [MCP Tool Safety](/docs/mcp-tool-safety) - [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](/docs/reference-service-accounts) --- 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-07-13T20:24:09+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`. | | `:mcp` | `:adapter`, `:tool`, optional `:arguments` | Deterministic outbound Streamable HTTP MCP tool call through a top-level `:mcp` adapter. Use `:tools {:mcp [...]}` on `:llm`/`:agent` when the model should choose the call. | | `: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. Direct `flow/step :files` calls are valid in orchestration; agent tool exposure only supports the safe subset directly, and broad repo/session ops such as `:resolve-source`, `:materialize`, `:capture`, `:heartbeat`, `:status`, `:release`, `:publish`, and `:open-change-request` need packaged `:steps` wrappers. | | `:fanout` | `:items`, optional `:max-concurrency`, `:on-error`, `:transform` | Async only for one child-workflow mode at a time: all `:call-flow` items or all named `:agent` 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 `:mode`, `:targets`, `:limit`, `:hydrate`, storage filters | Workspace search across `:resources`, `:flows`, `:runs`, and `:connections`; resource search defaults to hybrid retrieval and can opt into strict lexical or semantic-only retrieval. | | `: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 MCP](/docs/reference-step-mcp) | Deterministic outbound MCP tool calls through flow-level adapters | | [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-08-06T08:20:25+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` | | `:on-event` | map | No | Per-event emission for SSE responses: `{:connection ... :path ... :method ... :headers ... :max-events ...}`; see Streaming Events Out | | `:include-headers` | vector | No | Return only selected response headers, e.g. `["location"]` | | `:return-error-response?` | boolean | No | Return parsed 4xx/5xx responses as `{success false, status, body}` instead of failing the step | | `: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) | | `:on-error` | keyword/string or map | No | `:fail` (default), `:skip`, or `:continue`; use a map for checkpoint options | | `: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: - Default HTTP activity budget is 60 seconds for a single attempt and 90 seconds total. - `:timeout` raises both the HTTP client timeout and the internal activity budget up to the platform cap of 3600 seconds. - For slow synchronous calls, set `:timeout` to the maximum expected duration. - When `:retry` is present, Breyta uses the step-level retry loop and avoids hidden platform activity retries for that step; use `:retry {:max-attempts 1}` for non-repeatable calls. - 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. Failure handling: - HTTP failures (4xx/5xx and network errors) fail the step by default. - Use `:retry` for transient failures when the request is safe to repeat. - Use `:on-error :continue` to skip a failed HTTP step and continue the flow with a `nil` result. `:on-error :skip` is an equivalent explicit spelling. - Use `:return-error-response? true` when provider validation errors are an expected response. The step returns the parsed provider body with `:success false` and `:status`, so the flow can inspect structured error details without `:on-error` discarding them. Network failures still fail. - Use `:on-error {:strategy :checkpoint ...}` when a human should decide whether the step is retried. The map form also supports `:fail` and `:skip`. ```clojure (flow/step :http :optional-enrichment {:connection :api :path "/optional-enrichment" :on-error :continue}) ``` 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. - Accepted `:metering` keys are `:unit`, `:unit-cost-micros`, optional `:currency`, and optional `:label`. Use micros, not dollars, for the unit cost. For example, USD `$0.02` per request is `20000` micros: ```clojure (flow/step :http :firecrawl-agent {:connection :firecrawl :path "/v2/agent" :method :post :json {:prompt "Find pricing pages for these companies."} :response-as :json :metering {:unit :request :unit-cost-micros 20000 :currency "USD" :label "Firecrawl agent request"}}) ``` 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}`, `{:type :resource-ref :uri uri}`, and the older persisted `{:uri "res://..."}` envelope. - Resource URIs used with `:body-from-ref` / `:from-ref` must point at blob-backed resources in the same workspace. ## Streaming Events Out (`:on-event`) For finite `text/event-stream` responses, `:on-event` delivers each parsed SSE event to an external consumer while the step is still running, instead of only after the step completes. Each event is POSTed through a configured connection from inside the step's activity; the step's own result is unchanged (the full event vector under `:body`, exactly as without the hook). ```clojure (flow/step :http :stream-events {:connection :provider :path "/generate" :method :post :json {:prompt "..."} :response-as :sse :timeout 600 :on-event {:connection :my-ingest-api :path "/ingest" :max-events 200}}) ``` Configuration (`:on-event` map, closed): - `:connection` (required) - slot or connection id for the consumer endpoint. Emissions never go to an arbitrary URL. - `:method` - defaults to `:post`. - `:path` - appended to the connection base-url. - `:headers` - extra headers; `{:secret-ref ...}` values resolve as usual. - `:max-events` - per-attempt emission cap, clamped by the platform limit of `500` emissions per step. Behavior and semantics: - Delivery is at-least-once: activity retries re-run the stream, so consumers can see repeated events. Each payload carries an `attempt_id` plus a per-attempt `seq` for dedupe; consumers must be idempotent. - The payload shape is `{"workflow_id" ..., "step_id" ..., "attempt_id" ..., "seq" N, "event" {"event" ..., "data" ..., "id" ..., "json" ...}}`. - Emission failures never fail the step. The step result includes `:on-event {:emitted N :failed M :suppressed K :streamed true}`; failures are also logged. - Events past the emission cap are counted as `:suppressed`; the stream is still consumed fully so the step result stays complete. - If the response is not SSE (or not 2xx), the step takes its normal path and reports `:on-event {... :streamed false}`. - Emissions do not count against the per-run HTTP request limit; they are bounded by their own cap. - Set `:timeout` for real streams: the defaults (5 second socket idle, 60 second activity budget) end long-lived streams early. `:timeout` raises both. - `:on-event` cannot be combined with `:parser-fn`. ## 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 :initial-interval-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-07-27T14:15:22+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`, `:materialize`, `:capture`, `:heartbeat`, `:status`, `:release`, `:publish`, and `:open-change-request` in packaged steps when an agent needs those capabilities. - Use `:available-steps [:files]` or `:tools {:allowed ["files"]}` for the direct built-in files tool. Use `:tools {:steps [...]}` only for qualified packaged step ids such as `:github/publish`. - 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. Declare kebab-case keys for normalized config: JSON step params are normalized camelCase to kebab-case before validation (`siteUrl` arrives as `:site-url`), except inside the exact-shape payload subtrees (`:headers`, `:query`, `:query-params`, `:json`, `:body`, `:form`, `:form-params`, `:metadata`, `:context`), which keep caller key casing. | | `: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 :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-07-13T20:24:09+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, `:agents [...]` for direct flow-level agent delegation, and `:fanout {:agents [...]}` for bounded parallel specialist delegation | | `: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 | | `:timeout` | int | No | Common step timeout in seconds for long model calls | | `:retry` | map | No | Common retry policy; use `{:max-attempts 1}` for non-repeatable agent/model calls | 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`, `:materialize`, `:capture`, `:heartbeat`, `:status`, `:release`, `: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 ## Agentic Resource Search To let an agent reproduce the workspace asset search behavior used by Breyta's agent UI, expose the built-in `:search` tool and instruct it to use resource search with semantic or hybrid mode: ```clojure (flow/step :agent :answer-from-knowledge {:connection :ai :objective "Answer the user's question from workspace transcripts. Search before answering." :inputs {:question (:question (flow/input))} :instructions "Use the search tool with targets=[resources], mode=hybrid, and hydration enabled. Cite only retrieved content." :available-steps [:search] :max-iterations 6 :max-tool-calls 12}) ``` The search tool accepts the same resource knobs as `:search`: `query`, `mode`, `targets`, `storage-backend`, `storage-root`, `path-prefix`, `limit`, and `hydrate`. Use `mode=hybrid` for most Q&A flows. Use `mode=semantic` only when semantic resource search is known to be enabled; strict semantic mode fails loudly when the semantic index is unavailable. ## 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. - Each configured tool call that dispatches from the agent counts as a step execution for per-run limits and billing. Unknown tool names rejected before dispatch do not count. - 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. When the coordinator should choose several sibling specialists during its own reasoning, prefer the agent-internal fanout tool: ```clojure '(flow/step :agent :coordinator {:objective "Review the repo. Fan out to specialists when parallel review is useful, then synthesize the collected results." :inputs {:repo-tree repo-tree} :tools {:fanout {:agents [:review/security :review/performance] :max-items 5 :max-concurrency 3 :on-error :collect}} :max-iterations 8}) ``` The coordinator sees `fanout_agents` as one tool. Its arguments are a bounded `items` array of `{agent, input}` objects. The runtime validates each selected agent against the configured allowlist, starts the specialist agents as child workflows, and returns the collected success/failure result as the tool output. When the parent flow already knows the sibling agents to run deterministically, use flow-level `:fanout` with named `:agent` items: ```clojure (flow/step :fanout :parallel-review {:items [{:type :agent :agent :review/security :input {:area "auth" :repo-tree repo-tree}} {:type :agent :agent :review/performance :input {:area "queries" :repo-tree repo-tree}}] :max-concurrency 2 :on-error :collect}) ``` Each item starts a child workflow for the current flow and executes only the selected top-level `:agents` definition. The child run keeps normal run lineage, status, retry, timeout, and output-size behavior. Both patterns are valid; use the agent-internal tool for dynamic coordinator decisions and flow-level `:fanout` for deterministic orchestration. For the complete guide, see [Orchestration Agent Fanout](/docs/guide-orchestration-agent-fanout). ## Timeout Controls `:agent` runs its model work through the same activity path as `:llm`. The default activity budget is 240 seconds for a single attempt and 300 seconds total, and `:timeout` is capped at 900 seconds. For long synchronous search/model calls, set `:timeout` to the maximum expected duration; add `:retry {:max-attempts 1}` when the call should not be automatically retried. When `:retry` is present, Breyta uses the step-level retry loop and avoids hidden platform activity retries for that step. 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. Use this pattern when the agent should decide if and when to call the MCP tool. When orchestration already knows the exact remote tool and arguments, prefer `flow/step :mcp`; see [Step MCP](/docs/reference-step-mcp). ```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-08-08T07:35:34+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` - `:resources/url` - `:docs/find` - `:docs/show` Mutation operations: - `:flows/push-draft` requires `:mutations :draft-only` or `:write` - `:flows/update` requires `:mutations :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`, `: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/update` | `:flow-slug` string/keyword | `:name`, `:description`, `:publish-description`, `:tags`, `:archived`, `:publish-media`, `:primary-display-connection-slot` | | `:flows/run` | `:flow-slug` string/keyword | `:input` map, `:installation-id` string, `:target` `:draft`/`:live`, `:source` `:active`/`:draft`/`:latest`/`:version`, `:version` integer | | `:runs/list` | None | `:flow-slug` string/keyword, `:installation-id` string, `:version` integer, `:status` string/keyword, `:scope` `:live`/`:end-user`/`:all` (defaults to `:live`), `: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` | | `:resources/url` | `:uri` string | `:ttl` integer seconds, 60-86400, defaults to 3600 | | `: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 | `:runs/list` defaults to `:scope :live`: the workspace's own runs, including runs of apps this workspace installed. Runs the workspace merely hosts for other tenants — end-user profile installs and installer workspaces of its public apps — are excluded so pickers and pollers never read another tenant's run. Pass `:scope :end-user` to list hosted end-user profile runs, or `:scope :all` for everything. An explicit `:installation-id` targets that installation directly and bypasses the scope filter. For `:flows/run`, use `:target :draft` to test an unreleased draft. Use `:target :live` or `:installation-id` when manually smoke-testing or debugging a released/public installation target. Do not use `:breyta :flows/run` as the default wrapper-flow composition path. When an authored Breyta flow needs to call an installed flow as a building block, use `flow/call-flow` with `:installation-id`, or `:fanout` with `:call-flow` items for bounded batches. 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 :flows/update :args {:flow-slug :lead-research :publish-media {:type :image :source {:kind :flow-resource :uri "res://v1/ws/ws-acme/result/run/wf/cover-image"} :alt "Generated cover image"}}} {:op :resources/read :args {:uri "res://v1/ws/ws-acme/result/table/tbl_leads" :format :json}} {:op :resources/url :args {:uri "res://v1/ws/ws-acme/result/run/wf/report-pdf" :ttl 3600}} ``` 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))}}) ``` Use `:resources/url` to hand a resource to a downstream consumer without proxying bytes through the flow. The result carries the signed URL under `:data` (`:url`, plus `:expires-at` and `:uri`): ```clojure (flow/step :breyta :sign-report-url {:op :resources/url :allow [:resources/url] :args {:uri "res://v1/ws/ws-acme/result/run/wf/report-pdf" :ttl 3600}}) ``` The signed URL is a transferable credential — anyone holding it can fetch the resource until it expires (max 24h). Grant `:resources/url` deliberately, especially in published flows. It is excluded from the `:allow :read` shorthand: flows must list `:resources/url` explicitly. ## 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-07-20T10:08:41+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`) | | `:anthropic` | map | No | Anthropic Messages-specific options (`:thinking`, `:output-config`) | | `: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 | | `:timeout` | int | No | Common step timeout in seconds for long provider calls | | `:stream` | boolean | No | Provider streaming toggle for supported providers; defaults to enabled | | `:stream-first-event-timeout-ms` | int | No | First streamed output grace, default `30000`, range `5000`-`120000` | | `:stream-idle-timeout-ms` | int | No | Inter-output idle timeout after the first streamed output, default `10000`, range `5000`-`60000` | | `:retry` | map | No | Common step retry policy for transient provider timeouts | | `:on-error` | keyword/string or map | No | `:fail` (default), `:skip`, or `:continue`; use a map for checkpoint options | \* Provide `:messages`, `:prompt`/`:system`, or `:input`. ## Limits And Behavior - Use either `:messages` or `:prompt`/`:system`. - Prefer templates for long prompts. - Default LLM activity budget is 240 seconds for a single attempt and 300 seconds total. - `:timeout` raises the internal activity budget when it is above the default and is capped at 900 seconds for LLM calls. Smaller values do not lower the default 240/300 second LLM activity budget. - For long synchronous model/tool calls, set `:timeout` to the maximum expected duration and set `:retry {:max-attempts 1}` when the provider call is not safe to repeat. When `:retry` is present, Breyta uses the step-level retry loop and avoids hidden platform activity retries for that step. - OpenAI Responses, Anthropic Messages, DeepSeek, OpenRouter, and Google Gemini calls stream by default when supported. Streaming lets Breyta fail retryable no-output stalls as `:llm-idle-timeout` instead of waiting for the full request timeout. Set `:stream false` to opt out for a step. - Heavy prompts may need a longer first output grace. Raise `:stream-first-event-timeout-ms` for large-prefill calls; raise `:stream-idle-timeout-ms` only when a model legitimately pauses between output deltas. `:timeout` controls the activity budget, not the default first-output grace. - If a long streaming call exceeds the activity budget, Breyta reports the run failure as `:timeout`. Low-level stream close messages such as `Inflater has been closed` are diagnostic artifacts from the interrupted provider response, not the primary run error. - For transient LLM/provider read timeouts, use the common step retry option when the operation is safe to repeat: `:retry {:max-attempts 3 :backoff-ms 1000 :max-interval-ms 30000}`. - Use `:on-error :continue` to skip a failed LLM step and continue the flow with a `nil` result. `:on-error :skip` is an equivalent explicit spelling; use `:on-error {:strategy :checkpoint ...}` when a human should decide. - Step `:retry` runs at workflow level, so the backoff wait does not consume one provider activity attempt. Do not combine `:retry` with `:on-error {:strategy :checkpoint}`. - `:tools` belongs on the step config, not inside templates. - In execute mode, each configured tool call that dispatches from the agentic loop counts as a step execution for per-run limits and billing. Unknown tool names rejected before dispatch do not count. - 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, adaptive thinking effort) - `: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). Use [Step MCP](/docs/reference-step-mcp) instead when the flow should call one known MCP tool deterministically without model choice. - 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`, `:anthropic`, `:bedrock`, and `:deepseek`. ## Anthropic Adaptive Thinking For Claude models that support adaptive thinking, `:reasoning-effort` maps to the Anthropic Messages request shape: ```clojure (flow/step :llm :solve {:connection :ai :provider :anthropic :model "claude-opus-4-8" :prompt "Solve this carefully." :reasoning-effort :max}) ``` Breyta sends `:thinking {:type "adaptive"}` and `:output_config {:effort "max"}` to Anthropic. The supported effort values are `:low`, `:medium`, `:high`, `:xhigh`, and `:max`. Use the Anthropic envelope when you need the provider-native shape: ```clojure (flow/step :llm :solve {:connection :ai :provider :anthropic :model "claude-opus-4-8" :prompt "Solve this carefully." :anthropic {:thinking {:type :adaptive} :output-config {:effort :xhigh}}}) ``` Legacy flat `:provider-opts` also works for these Anthropic controls: ```clojure {:provider-opts {:thinking {:type :adaptive} :output_config {:effort :max}}} ``` When Anthropic returns thinking or reasoning token usage, Breyta surfaces it in the result under `[:usage :thinking-tokens]` and `[:usage :reasoning-tokens]`. ## 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 operations you want the model to choose during the tool loop. The LLM step does not bind directly to a remote server; it selects from top-level `:mcp` adapters. When orchestration already knows the exact tool and arguments, prefer `flow/step :mcp` instead. See [Step MCP](/docs/reference-step-mcp). ```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" ;; Optional: expose reviewed remote tool descriptions/schema annotations. ;; Leave false/omitted for metadata discovered from an untrusted server. :trusted-metadata true :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. Remote MCP descriptions and schema annotations are hidden from the model unless the author sets `:trusted-metadata true` on the adapter or a specific adapter tool after reviewing that metadata. ## 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 MCP URL: https://flows.breyta.ai/docs/reference-step-mcp.md HTML: https://flows.breyta.ai/docs/reference-step-mcp Last updated: 2026-05-28T23:55:08+02:00 # Step MCP ## Quick Answer Use `flow/step :mcp` when the flow should deterministically call one named tool on a configured remote MCP adapter. Use `:tools {:mcp [...]}` on `:llm` or `:agent` when the model or agent should decide if and when to call the MCP tool. Outbound MCP steps support Streamable HTTP MCP endpoints only. The remote MCP server is still bound through a normal `:http-api` requirement with `: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}}] :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}] :flow '(let [issues (flow/step :mcp :fetch-linear-issues {:adapter :linear/issues :tool "list_issues" :arguments {:team_id (-> (flow/input) :team-id)}})] issues)} ``` The step sends a JSON-RPC `tools/call` request to the configured MCP endpoint. The result shape matches agentic MCP tool calls: ```clojure {:mcp/tool "list_issues" :mcp/adapter-id :linear/issues :mcp/transport :streamable-http :mcp/is-error false :content "..." :result {...} :status 200} ``` ## Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `:adapter` | keyword/string | Yes | Top-level `:mcp` adapter id, such as `:linear/issues`. | | `:server` | keyword/string | No | Compatibility alias for `:adapter`; can also match the adapter connection slot. Prefer `:adapter` in new flows. | | `:tool` | string/keyword | Yes | Remote MCP tool name. Hyphens are normalized to underscores for parity with agentic MCP tool refs. | | `:tool-name` | string/keyword | No | Alias for `:tool`. | | `:arguments` | map | No | JSON-object-shaped arguments passed to the MCP tool. Defaults to `{}`. | | `:input` | map | No | Alias for `:arguments`. Prefer `:arguments` in new flows. | | `:timeout-ms` | int | No | Per-call timeout override in milliseconds. Defaults to adapter `:timeout-ms`, then 190000. | | `:max-result-bytes` | int | No | Per-call response-size override. Defaults to adapter `:max-result-bytes`, then 200000. | Common step options such as `:retry`, `:timeout`, `:on-error`, `:persist`, `:review`, and `:confirm` are also accepted. Use the authored step shape above in flow definitions. Normalized MCP activity fields such as `:connection` plus `:mcp-tool-name` are internal runtime payloads used by agentic MCP tools, and are not a public `flow/step :mcp` source shape. When both are present through runtime internals, the authored step path resolves the top-level adapter and enforces its allow/deny policy before any remote call. `:timeout` is the common step/activity timeout in seconds. `:timeout-ms` is the MCP HTTP client timeout sent to the Streamable HTTP call. If `:timeout` is set on the step, it is preserved as the activity timeout instead of being replaced by the adapter `:timeout-ms` default. ## Adapter Requirements Declare remote MCP servers in the top-level `:mcp` adapter list. The adapter connects the flow to a normal `:http-api` requirement: ```clojure :requires [{:slot :meta-mcp :type :http-api :label "Meta Marketing MCP" :backend :mcp :base-url "https://example-mcp-host.invalid/mcp" :auth {:type :bearer} :provided-by :installer}] :mcp [{:id :meta/marketing :connection :meta-mcp :transport :streamable-http :allow-tools ["ads_insights_performance_trend" "ads_get_ad_entities"] :deny-tools ["ads_update_entity" "ads_create_campaign"]}] ``` `flow/step :mcp` calls exactly the named tool. If the adapter has `:allow-tools`, the step tool must be in that list. If the adapter has `:deny-tools`, denied tools are rejected even when called directly. ## Choosing MCP Step Vs Agentic MCP Tools Prefer `flow/step :mcp` when: - The flow author knows the exact MCP tool to call. - The call belongs in deterministic orchestration. - The tool arguments are produced by previous steps or flow input. - You want retries, `:on-error`, persistence, and checkpoints around one known remote operation. Prefer `:tools {:mcp [...]}` on `:llm` or `:agent` when: - The model should choose whether to call the MCP tool. - The agent may need to call the tool multiple times while investigating. - The MCP tool is one capability among other agent tools. - You want model-visible tool schemas and agentic tool-loop tracing. Both patterns use the same top-level `:mcp` adapters and the same HTTP MCP runtime client. ## Limits And Security - Only `:transport :streamable-http` is supported for outbound flow runtime. - Local stdio MCP servers are not launched from flows. - Connections must use the `:http-api` requirement path with `:backend :mcp`. - MCP responses are bounded by `:max-result-bytes`. - Remote tool metadata is not needed for direct `:mcp` steps. For agentic MCP tools, remote descriptions/schema annotations remain untrusted unless the adapter or tool sets `:trusted-metadata true` after review. ## Related - [Flow Definition - MCP Tool Adapters](/docs/reference-flow-definition#mcp-tool-adapters) - [Step LLM - MCP Tools](/docs/reference-step-llm#mcp-tools) - [Step Agent - Using MCP Tools](/docs/reference-step-agent#using-mcp-tools) - [Step Reference](/docs/reference-step-reference) --- 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-06-17T08:36:29+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 or map | No | Input keys to load from blob refs before execution. JSON content loads as parsed data by default; use `{:field :text}` or `{:field :raw}` for raw UTF-8 text, or `{:field :bytes}` for the raw byte array (binary files such as docx/xlsx/pptx/pdf/images that must not be UTF-8 decoded). | | `: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`. - `:load [:field]` hydrates JSON resources as parsed data. For file-content APIs such as GitHub Contents updates, use `:load {:field :text}` so uploaded `application/json` files remain raw strings. - For binary files (Office docx/xlsx/pptx, pdf, images) use `:load {:field :bytes}` to receive the raw byte array, then `breyta.sandbox/base64-encode` it for APIs that take a base64 `data:` URI. `:raw`/`:text` UTF-8-decode and would corrupt non-UTF-8 bytes. - 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-07-13T20:24:09+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 ## Authored Flow Vs Agent Tool Boundary Raw `flow/step :files ...` calls are valid from normal authored flow orchestration. The canonical examples below use that form because the flow itself decides when to resolve a repository, fork a changeset, publish a branch, or open a pull request. The rule is different when exposing tools to an `:agent` or `:llm` step: - To expose the built-in files tool directly, use `:available-steps [:files]` or `:tools {:allowed ["files"]}`. Do not put `:files` in `:tools {:steps [...]}`; that list is for qualified packaged step ids. - Direct built-in 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`. - Broad repo/session operations are not exposed directly as agent tools: `:resolve-source`, `:materialize`, `:capture`, `:heartbeat`, `:status`, `:release`, `:publish`, and `:open-change-request`. Wrap those operations in top-level packaged `:steps` with qualified ids, explicit `:input-schema` values, and `:prepare` functions. Then publish those wrappers through `:tools {:mode :execute :steps [:github/resolve-source :github/publish ...]}` or call them directly from orchestration. If you copy a raw `:files` example into an agent-facing tool surface, keep the safe edit/read operations on the built-in files tool and package the repo source, materialize/capture, publish, and pull-request operations. ## 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`, `:gitlab`, 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 and GitLab sources, the resolved connection must currently be an `:http-api` connection so the runtime can use the provider REST/archive import path - provider-backed imports accept HTTPS, SCP-style SSH, and `ssh://` repository identity forms; API calls still use the bound connection origin - for self-managed GitLab, set `:source.provider :gitlab` and configure the connection `:base-url` to the GitLab `/api/v4` endpoint - local repositories and git remotes without a built-in provider use direct git import when no connection is supplied - connection-backed imports for other forges 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. - GitLab sources with a bound `:http-api` connection use GitLab REST/archive reads, including API-key header auth such as `PRIVATE-TOKEN`. - GitLab API requests use the bound connection `:base-url`; the repo URL supplies the group/project path. - GitHub and GitLab `401`/`403` can retry anonymously for public repositories. - Anonymous retry does not bypass private repo auth. - Provider credential failures keep `connection-id` in error context. - local repositories and git sources without a built-in provider 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}) ``` Agent-facing repo-edit flow with packaged repo operations: ```clojure {:functions [{:id :prepare-resolve-source :language :clojure :code "(fn [{:keys [repo ref]}] {:op :resolve-source :source {:type :git :repo repo :ref (or ref \"main\") :connection :github-api}})"} {:id :prepare-publish :language :clojure :code "(fn [{:keys [changeset commit-message]}] {:op :publish :changeset changeset :connection :github-api :repository \"acme/service\" :base-branch \"main\" :branch \"codex/docs-fix\" :commit-message commit-message})"} {:id :prepare-open-change-request :language :clojure :code "(fn [{:keys [published change-title change-body]}] {:op :open-change-request :connection :github-api :published published :change-title change-title :change-body change-body})"}] :steps [{:id :github/resolve-source :type :files :description "Resolve a GitHub repository into a source-tree resource." :input-schema [:map [:repo :string] [:ref {:optional true} :string]] :prepare :prepare-resolve-source} {:id :github/publish :type :files :description "Publish a prepared changeset to a GitHub branch." :input-schema [:map [:changeset :any] [:commit-message :string]] :prepare :prepare-publish} {:id :github/open-change-request :type :files :description "Open or reuse a GitHub pull request for a published branch." :input-schema [:map [:published :any] [:change-title :string] [:change-body :string]] :prepare :prepare-open-change-request}] :flow '(let [repo-tree (flow/step :github/resolve-source :resolve-repo {:repo "https://github.com/acme/service.git" :ref "main"}) draft (flow/step :files :start-draft {:op :init-changeset :source repo-tree}) draft (flow/step :files :edit-readme {:op :replace :changeset draft :path "README.md" :match "old setup" :replace "new setup"}) diff (flow/step :files :preview-diff {:op :diff :source draft}) published (flow/step :github/publish :publish-draft {:changeset draft :commit-message "docs: update setup instructions"}) pr (flow/step :github/open-change-request :open-pr {:published published :change-title "Update setup instructions" :change-body "Automated repo edit from the docs fixer flow."})] {:diff diff :pull-request pr})} ``` If the agent should choose when to publish or open the PR, publish the qualified packaged ids in the agent config: ```clojure {:tools {:mode :execute :allowed ["files"] :steps [:github/publish :github/open-change-request]}} ``` ## Source Providers `:files` routes provider-specific operations through a source provider registry. GitHub and GitLab are built-in source import providers. Provider archive imports are used for connection-backed sources; unconnected HTTPS git sources still use direct git clone. GitHub/GitLab providers are auto-detected from recognized repository URLs when a connection is supplied. Other forges must be set explicitly via `:provider` after their provider has been registered: ```clojure ;; Auto-detected as GitHub from the URL and imported through the bound API connection {:op :resolve-source :source {:type :git :repo "https://github.com/acme/service.git" :connection :github-api}} ;; Auto-detected as GitLab from an ssh:// identity URL and imported through the bound API connection {:op :resolve-source :source {:type :git :repo "ssh://git@gitlab.com/acme/service.git" :connection :gitlab-api}} ;; Explicit GitLab provider with a connection-backed private import {:op :resolve-source :source {:type :git :repo "https://gitlab.com/acme/private-service.git" :provider :gitlab :connection :gitlab-api}} ``` GitHub is the built-in provider for source import, publish, and pull-request operations. GitLab is built in for source import only. Custom providers can be registered for GitLab write operations, 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"}]} ;; Installer provides their own GitLab personal access token: {:requires [{:slot :gitlab-api :type :http-api :label "GitLab API" :auth {:type :api-key :location :header :param-name "PRIVATE-TOKEN"} :base-url "https://gitlab.com/api/v4"}]} ;; 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}}) (flow/step :files :resolve-gitlab-repo {:op :resolve-source :source {:type :git :repo "https://gitlab.com/acme/service.git" :ref "main" :connection :gitlab-api}}) ``` For GitHub, the same connection is used for `:publish` and `:open-change-request`. GitLab publish and merge-request operations require a custom files provider until built-in GitLab write support is added. 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-27T10:15:58+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: `1500` - 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 "