Docs
Workflows Overview
Workflows Overview
What workflows are, how they differ from chat, and the full lifecycle.
Chat vs Workflow
| Chat | Workflow | |
|---|---|---|
| Trigger | User message | Scheduled cron or manual run |
| Execution | Real-time, streaming | Background, step-by-step via QStash |
| Duration | Bounded by one serverless invocation (~60s) | Unlimited — each step is a separate invocation |
| State | Ephemeral per message | Persisted in messages table (Chat-as-State) |
| Human input | Every turn | Optional approval gates at specific steps |
Workflow Lifecycle
POST /api/workflows (goal + schedule)
→ LLM generates plan (WorkflowPlanSchema)
→ Save workflow (status: "draft") + steps
→ Send plan approval email (24h JWT)
User clicks "Approve" in email
→ GET /api/workflows/[id]/approve?token=
→ status: "active"
→ Create conversation + workflowRun
→ Enqueue step 0 via QStash
QStash POSTs to /api/workflows/step
→ Execute step tool via ToolLoopAgent
→ Save response (saveAgentResponse)
→ If step has requiresApproval: true
→ Send step approval email (2h JWT)
→ status: "awaiting_approval"
→ Else enqueue next step
All steps done → status: "completed" → send completion email
Any step throws → status: "failed" → send failure email
Status State Machine
Workflow statuses (on the workflows table):
| Status | Meaning |
|---|---|
draft | Plan generated, awaiting user approval via email |
active | Approved, can be run manually or on schedule |
archived | Retired |
WorkflowRun statuses (on the workflowRuns table):
| Status | Meaning |
|---|---|
running | Currently executing steps |
awaiting_approval | Paused at a step pending human review |
approved | User approved via email, re-queued |
completed | All steps succeeded |
failed | A step threw an unrecoverable error |
Database Tables
| Table | Description |
|---|---|
workflows | The plan: goal, title, status, cron schedule |
workflowSteps | Immutable ordered steps with toolName, requiresApproval, approvalPrompt |
workflowRuns | One row per execution: tracks currentStepIndex and status |
conversations | Each run gets its own conversation — the state store |
Why QStash
Vercel serverless functions time out at 60 seconds on the Hobby plan (300 seconds on Pro). A workflow with 8 steps, each making LLM calls, would easily exceed this.
QStash solves this by executing each step as an independent HTTP POST to /api/workflows/step. The platform never executes more than one step per serverless invocation — the total workflow duration is unbounded.