Skip to main content

What is a Workflow?

A Workflow is a directed acyclic graph (DAG) of blocks connected by edges. Each block performs a specific action — calling an LLM, running code, evaluating a condition, or executing an agent. Data flows from block to block through the edges. Unlike orchestrations (where the coordinator decides what happens), workflows follow a fixed, predetermined path every time.

Block Types

The block registry accepts these canonical types (plus the back-compat aliases function for code and api_call for general_api):
BlockDescriptionKey Config
starterManual / API trigger — declares the workflow’s input variablesNone (variables are passed at execution time)
webhookHTTP trigger — exposes the workflow at POST /api/webhooks/{webhook_id} once deployed/armedwebhook_id (UUID, you mint it via API or the studio mints it for you), webhook_secret
agentRuns an existing agent with a messageagent_id, message (template with {{variables.x}} interpolation)
orchestrationDelegates to a multi-agent coordinatororchestration_id, message
codeExecutes custom Python or JavaScriptLanguage, source, input mappings
conditionBranches the flow based on a boolean expressionExpression evaluated against upstream outputs
splitParallel fan-out — runs downstream branches concurrentlyBranch selection rules
platform_apiCalls a platform resource (KB search, agent run, etc.)Resource type + parameters
general_apiCalls an external HTTP APIURL, method, headers, body template
responseReturns the workflow result back to the callerResult template referencing upstream block outputs
Unknown block types are rejected at PUT /api/workflows/{id}/graph with 400 Unknown block type.

Graph Execution

When you execute a workflow, the engine evaluates blocks in topological order. Each block receives the outputs of its upstream blocks as input. Condition blocks create branches — only the matching branch continues execution. The workflow finishes when all output blocks have been reached.
Workflow execution DAG: Input block → LLM block (processes input) → Condition block (branches based on result) → Branch A: Output A / Branch B: Output B. Blocks are connected by directional edges showing data flow.

Programmatic vs Copilot

You can build workflows two ways. The programmatic approach uses the PUT /api/workflows/{id}/graph endpoint to define blocks and edges as JSON. The Copilot approach uses natural language — describe what you want, and the AI copilot generates the workflow graph for you. Both produce the same underlying graph structure.

Variables and references

Workflow blocks pass data to downstream blocks through their output. To reference an upstream block’s output from another block’s config, use angle-bracket reference syntax:
<blockId.output>
<blockId.output.field>
<blockId.output.field.nested>
The resolver walks the dotted path against the upstream block’s output dict. Block IDs may contain letters, digits, underscores, hyphens, and spaces, so <Lookup Customer.output.email> is valid. Two contexts:
  • Inside a string (most block configs): every <blockId.output.field> placeholder gets substituted as a string. "Hello <fetch.output.name>!" becomes "Hello Alice!".
  • Inside a JSON value (e.g. general_api body, code inputs, agent block messages): if the entire string is one reference, the resolved value is returned with its original type (so you can pass a number, array, or object — not just a string). "<extract.output.tags>" returns ["foo", "bar"] rather than '["foo", "bar"]'.
The starter block’s variables surface as <starter.output.variableName>, and you’ll also see the older {{variables.x}} syntax in agent / code block templates — both work; the angle-bracket form is canonical and applies to every block. Unresolvable references (block not yet run, missing field) leave the placeholder string as-is so you can see what failed when inspecting block logs. For condition blocks, the expression evaluator uses a Python-safe AST subset — comparisons, BoolOp (and/or/not), BinOp (+/-/*//), attribute access, and subscripting. Function calls are not permitted.

Scheduling

When a workflow’s starter block has schedule_enabled: true in its config, deploying the workflow materializes a schedule_config on the workflow row and Celery’s scheduler tick (every 30 seconds) fires runs at the configured cadence. The starter block’s config takes:
FieldTypeNotes
schedule_enabledboolMust be true for any scheduling to happen
schedule_type"interval" | "cron"Default interval
schedule_timezoneIANA nameDefault UTC
schedule_start_at / schedule_end_atISO 8601Optional bounds; runs outside the window are skipped
schedule_max_runsintOptional cap; once reached, the workflow stops being scheduled
schedule_interval_valueintFor interval type. Minimum effective interval is 60 seconds even if you set a smaller value
schedule_interval_unit"minutes" | "hours" | "days"For interval type. Default minutes
schedule_croncron expressionFor cron type. Default 0 * * * * (top of every hour). croniter syntax
On /deploy, the platform reads these from the starter block, normalizes them into schedule_config on the workflow row, resets schedule_run_count to 0, and the tick worker picks it up. /undeploy clears schedule_config and stops further scheduled runs. Scheduled runs execute as if invoked through /execute — the starter block’s variables get whatever default values are configured (no per-run override is possible from the schedule itself).

Deployment & Webhooks

Workflows expose two activation modes for their webhook block:
  • Deploy (POST /api/workflows/{id}/deploy) — sets state = "deployed". The webhook accepts unlimited calls until you undeploy. Use this for production.
  • Arm (POST /api/workflows/{id}/arm) — leaves state = "internal" but opens a 10-minute window during which the webhook accepts exactly one call. After it fires (or expires), you must arm again. Use this for one-shot tests.
The webhook_id and webhook_secret live in the webhook block’s config — they’re stored on the block when you add it to the graph. The studio editor mints both client-side when you drag in a webhook block. Neither /deploy nor /arm returns secrets — /deploy returns {"ok": true, "state": "deployed"} and /arm returns {"ok": true, "armed_until": "<iso8601>"}. To retrieve credentials programmatically, fetch the workflow with GET /api/workflows/{id} and read them from the webhook block’s config.
Webhook securityEach webhook block has a fixed webhook_secret set when the block was created — it does not rotate on arm/deploy. External callers include it as a Bearer token in the Authorization header (preferred) or as a ?token= query parameter. There is no body-HMAC signature scheme and no replay-window beyond the 10-min arm TTL.

Next Steps

Workflows (Programmatic)

Build a workflow step by step using the API.

Workflows (Copilot)

Build a workflow using natural language.

Workflows API Reference

Full endpoint documentation.