Skip to Content
DashboardWorkflow Builder

Workflow Builder

The Curate-Me Workflow Builder is a visual editor for designing multi-step AI work. You drag nodes onto a canvas, wire them together, run them on a draft (no cost) or publish them to production, and watch every step stream through live with cost, audit, and approval baked in.

Workflows are tenant-scoped (each org sees only its own), version-controlled (publishing freezes a snapshot), and observable end-to-end (every execution writes to workflow_executions with status, duration, and cost).

Onboarding checklist

Use this when you open the builder for the first time. Every step links to the relevant feature below.

  • Open /workflows in the dashboard
  • On the empty canvas, pick one of the four poster actions: Start with AI / Use template / Add trigger / Import workflow (see Empty state)
  • Add at least one Agent or LLM Call node
  • Wire it to an Output node and connect the edges
  • Configure any required Variables (declare on the workflow, reference with {{name}})
  • Click Test Draft — runs the workflow in test mode at zero cost
  • Click the toolbar validation badge → Fix each issue from the popover (errors block publish)
  • Click Publish to freeze a versioned snapshot
  • Trigger a real Run and watch the execution stream
  • Review cost and failure rate under System → Operations → Workflows (or the palette stats footer’s deep link)

Five minutes from a blank canvas to a published, observable workflow is the target.

Quickstart — Your first workflow

Goal: build a three-node workflow that classifies an incoming message with an LLM and writes the verdict to an output.

  1. Click New workflow. Name it quickstart-classifier.
  2. From the left palette, drag a Trigger → Manual node onto the canvas.
  3. Drag an LLM Call node next to it. Wire the trigger’s output port to the LLM input port.
  4. Open the LLM node’s config panel and set:
    • Model: claude-haiku-4-5 (cheapest classifier)
    • System prompt: You classify support tickets as urgent/normal/spam. Return one word.
    • User prompt: {{ticket_text}}
  5. Drag an Output node onto the canvas and wire the LLM’s output to it.
  6. Open the Variables panel (right sidebar) and declare ticket_text as a string variable.
  7. Click Test Draft. Provide a test value for ticket_text (e.g. "My server is on fire — production is down") and run.
  8. Inspect the execution stream — you should see the LLM call complete with a token + cost breakdown.
  9. Click Publish. Your workflow now has a stable ID and version 1.
  10. Hit Run to fire it for real.

That’s the whole loop. Every advanced feature in this doc layers onto these primitives.

Empty state

When the canvas has zero nodes (a fresh workflow or after deleting every node), the canvas renders an editorial poster overlay with four action cards. The poster sits on a subtle orbital-rings motif and is the recommended starting point — it’s faster than hunting the right palette item for your first node:

ActionWhat it does
Start with AIOpens the natural-language workflow generator. Describe the workflow in plain English; the generator drafts a draft DAG you can edit. Featured card with prompt input + suggested chips.
Use templateOpens the template gallery (~20 curated templates: classification, ETL, sales research, support triage, etc.).
Add triggerDrops a default Trigger node onto the canvas. Use when you already know the shape of the workflow and just want to start wiring.
Import workflowNavigates to the workflows list page where the JSON import dialog lives.

The poster auto-hides as soon as the first node is dropped. Panning, scrolling, and drag-drop onto the canvas still work through the overlay (pointer-events: none on the backdrop). Reduced-motion is honored — no entrance animation, no orbital rotation.

Node catalog

The launch-set ships nine node types. They group into four roles: control flow, work, integration, and meta.

NodeRoleUse when…
TriggerControlYou need an entry point. Manual button, webhook, or schedule (cron).
InputControlYou want a typed input port on the canvas (e.g. for testing).
OutputControlThe terminal node — collects the final payload returned to caller.
LLM CallWorkA single prompted LLM invocation. The bread and butter of most workflows.
Tool UseWorkInvoke a registered tool (HTTP fetch, MongoDB query, custom function).
AgentWorkA higher-level container running one of the registered agents end-to-end.
ConditionControlBranch on a JSONPath/expression — $.score > 0.7 style.
LoopControlIterate over an array. The loop body executes once per item.
ApprovalIntegrationPause the run until a named approver clicks Approve/Reject in the dashboard.
TransformWorkReshape data between nodes (pure JSON / template ops, no LLM cost).
Error handlerControlCatch downstream failures and route them to recovery logic.
MCP Server / Tool / ResourceIntegrationInvoke MCP-served tools — beta, see Beta nodes below.
Sticky noteMetaDocumentation on the canvas. No execution cost.

Each node’s exact configuration schema is rendered in the right-hand inspector when the node is selected. Required fields are marked with an asterisk and block publish until populated.

Beta nodes

Five node types ship as launch-set but flagged Beta because the runtime path is partial or stubbed:

NodeWhy it’s beta
LoopIterates over an input array; the body-execution path is still being completed.
Error handlerCatches upstream errors; downstream recovery flows are being completed.
MCP ServerConnector framework wired; some server calls return placeholder data.
MCP ToolConnector framework wired; some tool calls return placeholder data.
MCP ResourceConnector framework wired; some resource reads return placeholder data.

In the palette, beta nodes render a teal Beta badge with a per-node tooltip describing the specific gap. When a workflow contains any beta node, validation emits a uses_beta_node warning at save time — it does not block publish, but it surfaces as a banner on the workflow’s detail page and as an entry in the validation popover (see Validation and cost policies).

The classification lives in apps/dashboard/lib/workflow/node-categories.ts (NODE_LAUNCH_STATUS). Promoting a node to stable happens by editing that map; the backend launch-set validator in services/backend/src/services/workflow_validation.py is kept in sync intentionally.

Variables

Workflows can declare typed variables in the Variables panel:

  • Declare a variable with a name, type (string, number, boolean, json), and optional default.
  • Reference it anywhere in a config field with {{name}} syntax — system prompts, user prompts, tool URLs, and condition expressions all support template interpolation.
  • Variables are resolved per execution. Manual runs prompt you for values; webhook + schedule runs receive them from the trigger payload.
{ "variables": [ { "name": "ticket_text", "type": "string", "required": true }, { "name": "min_confidence", "type": "number", "default": 0.7 } ] }

Secrets (API keys, webhook secrets) should be stored in the org’s Settings → Secrets vault and referenced by name. They never appear in the canvas JSON.

Inspector

Selecting any node opens the right-hand inspector. Six tabs cover the full authoring + debugging surface:

TabWhat it shows
ConfigSchema-driven controls for the node’s configuration. Required fields marked with an asterisk; invalid values surface inline.
InputsTyped input ports with their resolved types. Each row click-throughs to the upstream producer node.
OutputsTyped output ports + downstream consumer count + a preview of the value emitted on the most recent execution.
RunsPer-node execution history (latest 10 across recent workflow runs). Each row is expandable to show the raw event payload — useful for diffing flaky runs.
PolicyOrg-governance view: model allowlist, HITL thresholds, cost limits, PII/tool restrictions that apply to this node. Read-only here; edited under Governance Policies.
JSONEditable JSON view of the node’s full config. Secret-looking fields are masked (••••) by default; reveal-with-confirm exposes them for a short window. Edits round-trip back through the schema validator before persisting.

Tabs are sticky across selections, so debugging multiple nodes in turn (e.g. cycling through a failing branch) keeps you on the same tab.

Triggers and run modes

A workflow can be invoked four different ways. Each one is wired through the same execution engine and produces an identical workflow_executions record.

Manual

Click Run in the dashboard. The dashboard prompts for any declared variables and starts the execution. Useful for ad-hoc work and demoing.

Webhook

Each published workflow gets a stable webhook URL of the form:

POST https://api.curate-me.ai/api/v1/admin/workflows/{workflow_id}/webhook X-CM-Webhook-Secret: <generated-per-workflow> Content-Type: application/json { "input_data": { "ticket_text": "Production is on fire" } }

The webhook secret is rotated from the workflow’s Settings → Webhook tab. Requests without it are rejected at the door (no governance chain spent).

Schedule

Cron-style scheduling. Configure under Settings → Schedule on a published workflow. The scheduler enforces tenant isolation — schedules cannot fire workflows owned by another org even if the cron is misconfigured.

Test Draft vs Publish vs Run

ActionSpends cost?Persists execution?Validation?
Test DraftNo — runs in test mode, mocks LLM responses by defaultMarked test=true, ignored by analyticsFull validation runs
PublishNo — doesn’t execute, freezes a versionNo execution rowHard-blocks on validation errors
RunYes — real LLM calls, real tool side-effectsYes — feeds the observability dashboardValidation re-runs at start; cost policy can still halt the run

Test Draft is the right answer 95% of the time during development. Reserve Run for the moment you actually want the side effects.

Approvals

Drop an Approval node anywhere downstream of a Trigger to require human sign-off mid-run.

Configure on the node:

  • approvers — list of user IDs or group IDs that can approve.
  • timeout_seconds — auto-cancel after this many seconds with no decision.
  • cost_threshold_usd — optional, only require approval when accumulated cost exceeds this.

Runtime behavior:

  1. Execution reaches the Approval node and transitions to paused.
  2. All listed approvers receive a notification (Slack / Teams / email — whichever channels their org has wired).
  3. The run is visible in Approvals in the dashboard with full input/output context up to that point.
  4. An approver clicks Approve (with optional comment + modified output) or Reject.
  5. On approve, the workflow resumes from the approval node. On reject, it terminates with status cancelled.

Approval state is persisted on the execution row, so a dashboard restart or browser refresh never loses the decision.

Validation and cost policies

Two gates stand between a draft and a successful publish:

Structural validation runs every time you save the canvas:

  • Every required port must be wired.
  • Variable references must match a declared variable.
  • No cycles in the DAG (loops express iteration as a node).
  • Every condition expression must parse.

Cost policy validation runs on Publish:

  • Estimated worst-case cost per run must fit under the workflow’s cost_limit.
  • The org’s per-day budget must have headroom (or cost_policy: warn).
  • Approver lists for Approval nodes must resolve to real users.

Validation popover

The toolbar shows a validation badge — green when clean, amber for warnings, red with an error count when publish is blocked. Clicking the badge opens the validation issues popover anchored beneath it. The popover lists every issue with a severity icon and a per-issue Fix button:

  • For node-scoped issues (missing config, unresolved variable, beta-node warning), Fix selects the offending node and scrolls it into view; the inspector switches to the relevant tab.
  • For edge-scoped issues (unwired required port), Fix highlights the edge and pans to it.
  • For workflow-scoped issues (cost ceiling, approver resolution), Fix focuses the corresponding setting in the right sidebar.

Errors block publish; warnings publish anyway and surface as a banner on the published workflow’s detail page. The popover is the recommended path to resolution — it’s faster than scanning the canvas by eye and consolidates errors, warnings, and beta-node notices in one place.

Tenant isolation

Every workflow carries an org_id and every execution is filtered by the caller’s resolved tenant. Concretely:

  • Listing workflows only returns the caller’s org’s workflows.
  • Loading a foreign workflow ID returns 404 (not 403 — we don’t leak existence).
  • The webhook URL embeds the workflow ID, but the secret is per-org and the executor double-checks the org_id before queuing the run.
  • The observability stats endpoint (GET /api/v1/admin/workflows/stats) returns zeroed data for orgs with no executions, never another org’s roll-up.

This is enforced both at the service layer (workflow_service.py) and the route layer (admin_workflows.py); the regression test suite at tests/api/test_workflow_tenant_isolation.py covers every route that takes a workflow ID.

Debugging

When something goes wrong (and it will), the workflow builder gives you five progressively-detailed tools.

Live execution stream

Hit Run and the canvas overlays a live SSE stream onto each node. You see:

  • Node transitions: queued → running → streaming → complete / failed.
  • Token + cost deltas as they accumulate.
  • The full prompt and response per LLM node (collapsible).
  • True / False edge pills pulse teal as control flows through them.

Inspector Runs tab

Select any node and switch to the inspector’s Runs tab to see that node’s latest 10 executions across recent workflow runs. Each row expands to its raw event payload so you can diff a working run against a failing one without leaving the canvas. See Inspector for the full tab catalog.

Policy view

The inspector’s Policy tab shows the org governance that applies to the selected node (model allowlist, HITL thresholds, cost limits, PII / tool restrictions). When a run is blocked by policy, this is where you confirm which rule fired before opening the Governance Policies page to amend it.

Execution history

Every workflow has a History tab listing every run ever, with status, duration, cost, and triggered_by. Click any row to load the full execution into a read-only canvas view.

Breakpoints and step-through

In test mode, you can right-click any node and select Add breakpoint. The next test run pauses before executing that node and exposes the in-flight context as JSON in the inspector. Click Step over to advance one node at a time, or Continue to run to completion.

Time-travel debugging

If a real-mode run failed and you want to retry from a specific point, open the execution and click Replay from here on any node. The replay re-executes downstream nodes only, with the upstream output frozen at the original values. See Time-Travel Debugging for the full feature.

Import and Export

Workflows round-trip through JSON.

Export: open a workflow and click Settings → Export → Download JSON. The output is the canonical workflow definition — nodes, edges, settings, variables, but never secrets (the export strips webhook secrets and any vault references).

Import: from the /workflows index page, click Import and paste/upload a JSON file. Imported workflows always start as draft with a fresh id, regardless of what the JSON declared.

Example minimal export:

{ "name": "quickstart-classifier", "description": "Classify support tickets", "variables": [ { "name": "ticket_text", "type": "string", "required": true } ], "nodes": [ { "id": "n1", "type": "trigger", "position": { "x": 0, "y": 0 }, "data": { "label": "Manual" } }, { "id": "n2", "type": "llm_call", "position": { "x": 250, "y": 0 }, "data": { "label": "Classify", "model": "claude-haiku-4-5", "config": { "system_prompt": "Classify as urgent/normal/spam.", "user_prompt": "{{ticket_text}}" } } }, { "id": "n3", "type": "output", "position": { "x": 500, "y": 0 }, "data": { "label": "Verdict" } } ], "edges": [ { "id": "e1", "source": "n1", "target": "n2" }, { "id": "e2", "source": "n2", "target": "n3" } ] }

Limits

Soft limits intended to keep runs sane:

LimitDefaultNotes
Nodes per workflow100Larger DAGs work but UI gets crowded; the visual builder is optimized for ≤ 50
Edges per workflow200Same caveat as nodes
Variables per workflow50Mostly an authoring-clarity limit
Execution duration30 minConfigurable per workflow via settings.timeout
Concurrent runs per workflowTier-dependentFree 1, Pro 10, Enterprise 100
Cost ceiling per run$1.00 defaultConfigurable per workflow via settings.cost_limit

Hitting a limit produces a structured error in the execution event stream — never a silent truncation.

Observability

Every published workflow run writes to workflow_executions with status, duration_ms, total_cost, and org_id. The dashboard surfaces this in three places:

  • /workflows/{id} History tab — the full execution log for one workflow.
  • /system/ops → Workflows tab — tenant-scoped roll-up of every workflow’s run health: failure rate, p50/p95 duration, total cost, top failing workflows. Switch the time window (24h / 7d / 30d) to scope the metrics.
  • /workflows/{id}/analytics — per-workflow drill-down with bottleneck analysis, path analytics, and funnel views.

Background: the system-ops Workflows panel hits GET /api/v1/admin/workflows/stats?window=24h|7d|30d. The endpoint is tenant-scoped, returns a zeroed payload for empty windows, and never leaks foreign-org executions — it’s the canonical place to wire alerts off of.

The node palette has a compact stats footer pinned at the bottom showing the same 24h roll-up at a glance: today’s runs, failure rate (tonal — good / warn / err based on < 5% / 5–15% / > 15%), p95 duration, and total cost. Auto-refreshes every 60 seconds while the builder is open. Clicking it deep-links to /system/ops?tab=workflows for the full breakdown. The footer is the fastest signal that “something is wrong out there” without leaving the builder.

API endpoints

All workflow APIs live under /api/v1/admin/workflows/* and require a valid JWT plus an admin role or enterprise tier.

MethodPathPurpose
GET/api/v1/admin/workflowsList workflows (paginated, tenant-scoped)
POST/api/v1/admin/workflowsCreate a new workflow (starts in draft)
GET/api/v1/admin/workflows/{id}Read one
PUT/api/v1/admin/workflows/{id}Update (draft only)
DELETE/api/v1/admin/workflows/{id}Soft delete
POST/api/v1/admin/workflows/{id}/publishFreeze a version
POST/api/v1/admin/workflows/{id}/executeStart a run
GET/api/v1/admin/workflows/{id}/executions/{exec_id}/streamSSE stream of execution events
POST/api/v1/admin/workflows/{id}/executions/{exec_id}/cancelCancel an in-flight run
POST/api/v1/admin/workflows/{id}/executions/{exec_id}/approveApprove a paused run
GET/api/v1/admin/workflows/statsTenant-scoped run health roll-up (24h/7d/30d)

Full OpenAPI is published at https://api.curate-me.ai/openapi.json and viewable at /docs when running locally.

Mobile and tablet

The builder is desktop-first but degrades cleanly down to tablet and phone widths. The shell switches structural variants at three breakpoints — same content, different layout:

WidthModeWhat changes
≥ 1024pxdesktopDefault Wave 13 shell — palette as a left rail, inspector as a right rail, full toolbar.
768–1023pxtabletPalette collapses to a 56px icon rail with a sheet drawer on tap. Inspector stays as a right rail. Toolbar collapses secondary actions into a More dropdown.
640–767pxmobileSame palette behavior as tablet. The inspector becomes a bottom sheet with peek + expanded states and a drag handle. Toolbar reduces to identity + Run.
< 640pxread-onlyCanvas locks to read-only. A banner explains “Open the builder on tablet or larger to edit.” Viewing, pan, zoom, and the execution stream still work.

The hook backing this lives at apps/dashboard/components/workflow-builder/useResponsiveBuilder.ts and is the single source of truth for the breakpoints — toolbar, inspector, palette, and canvas all read from it.

Caveats:

  • Drag-drop from the palette is unsupported below 1024px; on tablet/mobile, tap-to-add is used instead (the palette drawer’s node entries call the same onAddNode handler as drag).
  • Multi-select and group operations are desktop-only.
  • The 6-tab inspector is fully available on tablet (right rail) and mobile (bottom sheet, expanded state) — every tab works at every width.

Screenshot inventory

Production screenshots will be generated once visual-regression baselines are re-recorded post-implementation. Until then, the canonical visual references live in the Phase 2 Claude Design handoff bundle at apps/dashboard/design/workflow-builder-phase-2-handoff/:

SurfaceReference file
Empty-state posterempty-state.jsx
Mid-execution state (running workflow, status pills, inspector Runs tab)mid-execution.jsx
Validation popover + Fix affordancesvalidation-state.jsx
Mobile / tablet variants (palette drawer, inspector bottom sheet, toolbar More)responsive.jsx
Palette stats footerpalette-detail.jsx
Composed prototype (all 5 scenes)Workflow Builder.html

The handoff bundle is design reference only — HTML/CSS/JS prototypes, not production code. The production implementation in apps/dashboard/components/workflow-builder/ reproduces the visual intent using Orbital tokens. See the bundle’s README.md for context on the design medium.

Where to go next