Define Business Flows
This guide advertises Business Flows from your backend using the Qefro Backend Framework (@qefro-ai/backend, qefro-backend-sdk, or qefro-backend). A flow describes how your existing Business Tools are orchestrated — which questions to ask, which tools to call, and in what order.
Flows are metadata only in your backend. The SDK advertises them through the capabilities.list message; the Qefro Runtime discovers, validates, versions, and executes them. Nothing runs inside the SDK. Register your tools first with Register SDK Business Tools — a flow references those tools by name. Once a flow is enabled, see Run Business Flows for conversation-driven execution, or Event-Driven Triggers to start the same Runtime from connectors, webhooks, and schedules.
Outcome
- One or more flows declared with
app.flow(...)in your backend - Flows advertised alongside tools via
capabilities.list - Flows discovered on Sync Tools, validated, and shown in Admin Console → Business Tools → SDK Connections
- First discovery auto-accepts; later changes surface an Accept / Reject version prompt
Prerequisites
- A working SDK Connection with registered tools (see Register SDK Business Tools)
@qefro-ai/backend≥ 1.1.0 orqefro-backend-sdk≥ 1.2.0 orqefro-backend≥ 1.0.0- Owner/Admin access in Admin Console
Concepts
| Term | Meaning |
|---|---|
| Flow | { metadata, steps } describing an orchestration of Business Tools |
id | Immutable identity of the flow. Renaming name never creates a new flow |
| Step | { id, type, config } — a single node; every step needs a unique id |
trigger | Optional entry path: conversation (default), event, schedule, or webhook — see Event-Driven Triggers |
capabilities.list | Message that returns both tools and flows (supersedes tools.list) |
| Version detection | The Runtime computes its own checksum over each flow and prompts to Accept/Reject changes |
| Validation | The Runtime flags errors (block enabling) and warnings (informational) at sync time |
Step 1 — Declare a flow
Chain step builders off app.flow(metadata). Each tool step references an existing tool by tool_ref.
TypeScript
import { Qefro } from '@qefro-ai/backend';
const app = new Qefro({ signingSecret: process.env.QEFRO_SIGNING_SECRET! });
// ...register app.tool('lookup_customer', ...) and app.tool('get_orders', ...) first
app
.flow({
id: 'order_lookup', // immutable identity — renaming `name` never creates a new flow
name: 'Order Lookup',
description: 'Lookup customer orders',
version: 1,
category: 'crm',
tags: ['customer', 'orders'],
intent: ['track order', 'where is my order', 'find my shipment'],
inputs: ['email'],
outputs: ['customer', 'orders'],
})
.ask({ id: 'email', field: 'email', prompt: 'Please enter your email.' })
.tool({ id: 'lookup', tool_ref: 'lookup_customer' })
.tool({ id: 'orders', tool_ref: 'get_orders' })
.complete({ id: 'done', message: 'Here are your recent orders.' });
await app.listen({ port: 8088, path: '/qefro' });
Rust
use qefro_backend_sdk::BusinessFlowMetadata;
app.flow(BusinessFlowMetadata {
id: "order_lookup".into(),
name: Some("Order Lookup".into()),
description: Some("Lookup customer orders".into()),
category: Some("crm".into()),
tags: vec!["customer".into(), "orders".into()],
intent: vec!["track order".into(), "where is my order".into()],
inputs: vec!["email".into()],
outputs: vec!["customer".into(), "orders".into()],
..Default::default()
})?
.ask("email", "email", "Please enter your email.")
.tool("lookup", "lookup_customer")
.tool("orders", "get_orders")
.complete("done", Some("Here are your recent orders.".into()))?;
In Rust, flow(...) and .complete(...) return Result — a duplicate or empty id (flow or step) surfaces a FlowError and the flow is excluded from capabilities.list instead of panicking. In TypeScript, the same mistakes throw at startup.
Python
from qefro_backend import Qefro
app = Qefro(signing_secret="...") # or QEFRO_SIGNING_SECRET from env
# ...register app.tool("lookup_customer", ...) and app.tool("get_orders", ...) first
(
app.flow({
"id": "order_lookup", # immutable identity — renaming `name` never creates a new flow
"name": "Order Lookup",
"description": "Lookup customer orders",
"category": "crm",
"tags": ["customer", "orders"],
"intent": ["track order", "where is my order", "find my shipment"],
"inputs": ["email"],
"outputs": ["customer", "orders"],
})
.ask("email", field="email", prompt="Please enter your email.")
.tool("lookup", tool_ref="lookup_customer")
.tool("orders", tool_ref="get_orders")
.complete("done", message="Here are your recent orders.")
)
app.run(8088) # POST http://0.0.0.0:8088/qefro
In Python, a duplicate or empty id (flow or step) raises FlowError at declaration time. Use the else_ argument on .condition(...) (Python keyword) — it is serialized as the else wire key.
Optional entry trigger
By default a flow starts from a matching customer message (intent). To start from a bus event, schedule, or webhook instead, set trigger on the flow metadata:
app.flow({
id: 'abandoned_cart_recovery',
name: 'Abandoned cart recovery',
trigger: { type: 'event', event: 'shopify.cart.abandoned' }, // namespaced
// trigger: { type: 'schedule', cron: '0 2 * * *' },
// trigger: { type: 'event', event: 'shopify.order.created', when: 'total >= 1000' },
})
.tool({ id: 'remind', tool_ref: 'send_cart_reminder' })
.complete({ id: 'done' });
Event names must be namespaced (shopify.cart.abandoned, not cart_abandoned). Full emit, ordering, replay, and connector guidance: Event-Driven Triggers.
Step 2 — Step types
Every step is { id, type, config }. id must be unique within the flow. Type-specific settings live in config, so future options extend config without breaking the wire format.
| Type | Purpose | Key config |
|---|---|---|
ask | Collect input from the customer | field, prompt |
tool | Call a registered Business Tool | tool_ref |
challenge | Trigger a customer auth challenge | message? |
upload | Request a file | field?, prompt?, accept? |
condition | Branch to other steps | when, then?, else? |
delay | Wait before continuing | duration_seconds (or ISO until) |
approval | Require human approval | prompt? |
complete | Terminal step | message? |
Step 3 — Sync and review in Admin Console
- Open Business Tools → SDK Connections and select a workspace.
- Click Sync Tools. The Runtime calls
capabilities.list, imports tools as before, and discovers your flows. - Each connection shows a Business Flows section grouped by category with a Valid / Invalid badge, version info, tags, and inputs/outputs.
- Toggle Enabled per flow (disabled for invalid flows), and edit Prompt Overrides for
asksteps.
An enabled, valid, accepted flow is live: matching customer messages start a run the Runtime executes end to end — see Run Business Flows.
New flows are auto-accepted on first discovery. When you change a flow and re-sync, the Runtime detects the change (via its own checksum) and shows a New Flow Version Available banner with Accept and Reject. Accept promotes the pending version; Reject keeps the current one.
Versioning and identity
- The Runtime owns change detection — it computes a SHA-256 over its own canonical serialization of each received flow. The SDK never computes checksums.
- Identity is the immutable
id. Renamenamefreely: the flow updates in place, no new version. - Bump
versionfor your own record-keeping; the Runtime still uses its checksum to decide when an Accept/Reject prompt appears.
Protocol reference (what Qefro sends)
| Message | When | Your response |
|---|---|---|
ping | Test Connection | { "type": "pong", ... } |
capabilities.list | Sync Tools | { "type": "capabilities.list", "tools": [...], "flows": [...] } |
tools.list | Legacy Sync (tools only) | { "type": "tools.list", "tools": [...] } |
The framework verifies and signs requests for you when you use app.listen().
Workflow checklist
Business Flows launch
- Register tools — app.tool for every tool_ref your flow references.
- Declare flows — app.flow(metadata) + step builders; unique step ids.
- Sync Tools — Runtime calls capabilities.list and discovers flows.
- Review validation — Fix any Invalid flows (errors block enabling).
- Enable + tune — Toggle enabled; set ask-step prompt overrides.
- Accept changes — On later syncs, Accept/Reject new flow versions.