Workflows API

Workflows orchestrate multi-step agent execution across six patterns. Each workflow contains a definition with steps, optional loop or swarm configuration, and persisted execution state. Workflows manage their own task lifecycle — creating, executing, and monitoring step tasks automatically.

Quick Start

Create a workflow with sequential steps, execute it, poll for status, and check the results:

// 1. Create a sequence workflow with three steps
interface Workflow {
id: string;
name: string;
status: string;
runNumber: number;
}

const wfRes: Response = await fetch("http://localhost:3000/api/workflows", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
  name: "Content Pipeline",
  projectId: "proj-8f3a-4b2c",
  definition: {
    pattern: "sequence",
    steps: [
      {
        id: "research",
        name: "Research",
        prompt: "Research competitor pricing pages and summarize patterns",
        assignedAgent: "claude-code",
        agentProfile: "researcher",
      },
      {
        id: "draft",
        name: "Draft",
        prompt: "Write a pricing page draft based on the research findings",
        dependsOn: ["research"],
      },
      {
        id: "review",
        name: "Review",
        prompt: "Review the draft for clarity, accuracy, and tone",
        requiresApproval: true,
        dependsOn: ["draft"],
      },
    ],
  },
}),
});
const workflow: Workflow = await wfRes.json();
// → { id: "wf-4a7c-e2d1", status: "draft", runNumber: 0, ... }

// 2. Execute the workflow — returns 202 immediately
await fetch(`http://localhost:3000/api/workflows/${workflow.id}/execute`, { method: "POST" });

// 3. Poll for status until all steps are done
interface StepState { stepId: string; status: string }
interface WorkflowStatus { status: string; steps: StepState[] }

const poll = async (): Promise<string> => {
const statusRes: Response = await fetch(`http://localhost:3000/api/workflows/${workflow.id}/status`);
const status: WorkflowStatus = await statusRes.json();

console.log(`Workflow: ${status.status}`);
for (const step of status.steps) {
  console.log(`  ${step.stepId}: ${step.status}`);
}
return status.status;
};

// 4. Check output documents produced by the workflow
const docsRes: Response = await fetch(`http://localhost:3000/api/workflows/${workflow.id}/documents`);
const docs: { documentId: string }[] = await docsRes.json();

console.log(`${docs.length} documents produced`);

Base URL

/api/workflows

Patterns

PatternDescription
sequenceSteps execute in order, each waits for the previous to complete
parallelSteps execute concurrently with dependency tracking
loopSingle step repeats for configurable iterations with time/signal budget
planner-executorPlanning phase followed by execution phase
checkpointSteps with approval gates between execution stages
swarmConcurrent worker agents with configurable concurrency limit

Endpoints

List Workflows

GET /api/workflows

Retrieve all workflows with task count and output document count aggregation, ordered newest first.

Response Body (Array)

FieldTypeReqDescription
idstring (UUID)*Workflow identifier
namestring*Workflow name
projectIdstring (UUID)Associated project
definitionstring (JSON)*Serialized WorkflowDefinition
statusenum*draft, active, paused, completed, or failed
runNumbernumber*Execution iteration counter
taskCountnumber*Total step tasks created
outputDocCountnumber*Output documents produced
createdAtISO 8601*Creation timestamp
updatedAtISO 8601*Last modification

Fetch all workflows to build a pipeline overview — includes task and document counts:

// List all workflows with aggregated counts
const response: Response = await fetch("http://localhost:3000/api/workflows");
const workflows: Workflow[] = await response.json();

workflows.forEach((wf) => {
const def: { pattern: string } = JSON.parse(wf.definition);
console.log(`${wf.name} [${wf.status}] — ${def.pattern}, ${wf.taskCount} tasks, ${wf.outputDocCount} docs`);
});

Example response:

[
  {
    "id": "wf-4a7c-e2d1",
    "name": "Content Pipeline",
    "projectId": "proj-8f3a-4b2c",
    "status": "completed",
    "runNumber": 2,
    "taskCount": 3,
    "outputDocCount": 1,
    "createdAt": "2026-04-01T08:00:00.000Z",
    "updatedAt": "2026-04-01T09:15:00.000Z"
  }
]

Create Workflow

POST /api/workflows

Create a new workflow in draft state. The definition must include a valid pattern and at least one step.

Request Body

FieldTypeReqDescription
namestring*Workflow name (non-empty)
projectIdstring (UUID)Project to associate with
definitionWorkflowDefinition*Pattern, steps, and optional config

WorkflowDefinition

FieldTypeReqDescription
patternenum*sequence, parallel, loop, planner-executor, checkpoint, or swarm
stepsWorkflowStep[]*At least one step
loopConfigLoopConfigRequired for loop pattern
swarmConfigSwarmConfigOptional concurrency settings for swarm
sourceTaskIdstringTask that originated this workflow

WorkflowStep

FieldTypeReqDescription
idstring*Step identifier (unique within workflow)
namestring*Step display name
promptstring*Agent prompt for this step
requiresApprovalbooleanPause for human approval before executing
dependsOnstring[]Step IDs that must complete first
assignedAgentstringOverride agent runtime for this step
agentProfilestringOverride profile for this step
documentIdsstring[]Documents available to this step

LoopConfig

FieldTypeReqDescription
maxIterationsnumber*Maximum loop iterations
timeBudgetMsnumberTotal time budget in milliseconds
assignedAgentstringAgent runtime for loop iterations
agentProfilestringProfile for loop iterations
completionSignalsstring[]Agent output signals that end the loop early

Response 201 Created — Workflow with status: "draft", runNumber: 0

Errors: 400 — Invalid name, definition, or assignment validation failure

Create a parallel workflow where independent steps run concurrently, with a final step that depends on both:

// Create a parallel workflow — pricing and features run concurrently,
// synthesis waits for both to finish
const response: Response = await fetch("http://localhost:3000/api/workflows", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
  name: "Competitive Analysis",
  projectId: "proj-8f3a-4b2c",
  definition: {
    pattern: "parallel",
    steps: [
      { id: "pricing", name: "Pricing Research", prompt: "Analyze competitor pricing models" },
      { id: "features", name: "Feature Comparison", prompt: "Compare feature sets across top 5 competitors" },
      { id: "synthesis", name: "Synthesis", prompt: "Combine pricing and feature analysis into a final report", dependsOn: ["pricing", "features"] },
    ],
  },
}),
});
const workflow: Workflow = await response.json();

console.log(workflow.id);     // "wf-4a7c-e2d1"
console.log(workflow.status); // "draft"

Example response:

{
  "id": "wf-4a7c-e2d1",
  "name": "Competitive Analysis",
  "projectId": "proj-8f3a-4b2c",
  "status": "draft",
  "runNumber": 0,
  "taskCount": 0,
  "outputDocCount": 0,
  "createdAt": "2026-04-03T10:00:00.000Z",
  "updatedAt": "2026-04-03T10:00:00.000Z"
}

Update Workflow

PATCH /api/workflows/{id}

Edit a draft workflow's name or definition, or pause an active workflow. Active/paused workflows cannot be edited — only paused.

Mode A — Edit (draft, completed, or failed workflows only):

Request Body

FieldTypeReqDescription
namestringUpdated name
definitionWorkflowDefinitionUpdated definition

Mode B — Pause (active workflows only):

{ "status": "paused" }

Editing a completed or failed workflow resets it to draft and clears execution state.

Errors: 404 — Not found, 409 — Cannot edit active/paused workflow, invalid status transition

Update the name and add a step to a draft workflow:

// Rename a draft workflow before execution
await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Competitive Analysis v2" }),
});

Delete Workflow

DELETE /api/workflows/{id}

Delete a workflow and all related resources. Active workflows cannot be deleted — pause first.

Response 200{ "deleted": true }

Cascade deletes: usage ledger, agent logs, notifications, documents, learned context, tasks, then workflow.

Errors: 404 — Not found, 409 — Cannot delete active workflow

// Delete a workflow — must not be in active state
const res: Response = await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1", { method: "DELETE" });

if (res.status === 409) {
console.log("Pause the workflow first before deleting");
}

Execute Workflow

POST /api/workflows/{id}/execute

Start workflow execution. Returns 202 immediately while the workflow engine runs steps asynchronously. Increments runNumber on each execution.

Response 202 Accepted{ "status": "started", "workflowId": "..." }

For completed or failed workflows, re-execution resets the internal state and starts fresh.

Errors: 404 — Not found, 409 — Already running

Execute a workflow and track its progress — the engine creates tasks for each step automatically:

// Start the workflow — returns immediately while steps run in background
const res: Response = await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1/execute", {
method: "POST",
});

if (res.status === 202) {
const { workflowId }: { workflowId: string } = await res.json();
console.log("Workflow started — poll /status to track progress");
} else if (res.status === 409) {
console.log("Workflow is already running");
}

Example response:

{
  "status": "started",
  "workflowId": "wf-4a7c-e2d1"
}

Get Workflow Status

GET /api/workflows/{id}/status

Get detailed execution status including per-step states, loop progress, documents, and run history.

The response shape depends on the workflow pattern:

Non-loop patterns return steps[] with per-step state:

Step State Fields

FieldTypeReqDescription
stepIdstring*Step identifier
statusenum*pending, running, completed, failed, waiting_approval, or waiting_dependencies
taskIdstringAssociated task ID (once created)
resultstringStep execution result
errorstringError message if failed
startedAtISO 8601Step start time
completedAtISO 8601Step completion time

Loop patterns return loopState with iteration history:

Loop State Fields

FieldTypeReqDescription
currentIterationnumber*Current iteration index
iterationsarray*Per-iteration status, result, timing
statusenum*running, completed, paused, or failed
stopReasonenummax_iterations, time_budget, agent_signaled, human_cancel, human_pause, or error
totalDurationMsnumberTotal execution time

Both shapes include stepDocuments (per-task output files), parentDocuments (workflow-level inputs), and runHistory (per-run aggregation).

Errors: 404 — Not found

Poll workflow status to build a real-time progress tracker:

// Poll workflow status to display step-by-step progress
const response: Response = await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1/status");
const status: WorkflowStatus = await response.json();

console.log(`Workflow: ${status.status}, Run #${status.runNumber}`);

// Display per-step progress
for (const step of status.steps) {
const duration: string = step.completedAt && step.startedAt
  ? ((new Date(step.completedAt).getTime() - new Date(step.startedAt).getTime()) / 1000).toFixed(1) + "s"
  : "—";
console.log(`  ${step.stepId}: ${step.status} (${duration})`);
}

Example response for a running sequence workflow:

{
  "status": "active",
  "runNumber": 1,
  "steps": [
    {
      "stepId": "research",
      "status": "completed",
      "taskId": "task-b3e1-7f2a",
      "result": "Found 5 competitor pricing pages with common patterns...",
      "startedAt": "2026-04-03T10:01:00.000Z",
      "completedAt": "2026-04-03T10:03:22.000Z"
    },
    {
      "stepId": "draft",
      "status": "running",
      "taskId": "task-d9c4-1a8e",
      "startedAt": "2026-04-03T10:03:23.000Z"
    },
    {
      "stepId": "review",
      "status": "waiting_dependencies"
    }
  ],
  "stepDocuments": {},
  "parentDocuments": [],
  "runHistory": []
}

List Workflow Documents

GET /api/workflows/{id}/documents

List all document bindings for a workflow, including step-scoped and workflow-level documents.

Response Body (Array)

FieldTypeReqDescription
bindingIdstring (UUID)*Binding record ID
documentIdstring (UUID)*Document ID
stepIdstringStep scope (null = all steps)
documentobjectDocument metadata (name, MIME type, size, direction, status)

List all documents attached to a workflow, including step-scoped and workflow-wide bindings:

// List workflow documents to see inputs and outputs
const response: Response = await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1/documents");
const docs: { bindingId: string; documentId: string; stepId?: string }[] = await response.json();

// Separate workflow-wide vs step-scoped documents
const globalDocs = docs.filter((d) => !d.stepId);
const scoped = docs.filter((d) => d.stepId);
console.log(`${globalDocs.length} global, ${scoped.length} step-scoped documents`);

Attach Documents

POST /api/workflows/{id}/documents

Attach documents to a workflow. Optionally scope to a specific step. Duplicate bindings are silently ignored.

Request Body

FieldTypeReqDescription
documentIdsstring[]*Document UUIDs to attach (non-empty)
stepIdstringScope to a specific step (null = all steps)

Response 201 Created{ "attached": 2, "workflowId": "...", "stepId": null }

Errors: 400 — Empty array, 404 — Workflow or documents not found

Attach reference documents to a specific step — only that step’s agent will see these files:

// Attach documents scoped to the research step only
const response: Response = await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1/documents", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
  documentIds: ["doc-competitor-data-q4", "doc-pricing-spreadsheet"],
  stepId: "research",
}),
});
const { attached }: { attached: number } = await response.json();

console.log(`Attached ${attached} documents to research step`);

Remove Documents

DELETE /api/workflows/{id}/documents

Remove document bindings. Pass specific IDs to remove selectively, or omit to remove all bindings.

Request Body (optional)

FieldTypeReqDescription
documentIdsstring[]Specific documents to unbind
stepIdstringFilter removal by step

Response 200{ "ok": true }

// Remove a specific document binding
await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1/documents", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ documentIds: ["doc-competitor-data-q4"] }),
});

Retry Step

POST /api/workflows/{id}/steps/{stepId}/retry

Retry a failed workflow step. Returns 202 while the retry runs asynchronously.

Response 202 Accepted{ "status": "retry_started", "workflowId": "...", "stepId": "..." }

Retry a failed step without re-running the entire workflow:

// Retry a failed step — the workflow engine picks up from here
const res: Response = await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1/steps/research/retry", {
method: "POST",
});

if (res.status === 202) {
console.log("Retry started — downstream steps will re-execute when this completes");
}

Create from AI Assist

POST /api/workflows/from-assist

Create a workflow with pre-built step tasks in a single atomic operation. Optionally execute immediately.

Request Body

FieldTypeReqDescription
namestring*Workflow name
projectIdstring (UUID)Project association
definitionWorkflowDefinition*Pattern and steps
prioritynumberDefault priority for step tasks(default: 2)
assignedAgentstringDefault agent for steps
executeImmediatelybooleanStart execution after creation

Response Body

FieldTypeReqDescription
workflowobject*Created workflow
taskIdsstring[]*IDs of created step tasks
statusenum*created or started

Errors: 400 — Validation failure

Create a workflow and start it immediately in one call — useful for AI-assisted task decomposition:

// Create and execute in one call — no need for a separate execute request
const response: Response = await fetch("http://localhost:3000/api/workflows/from-assist", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
  name: "Quick Analysis",
  definition: {
    pattern: "sequence",
    steps: [
      { id: "analyze", name: "Analyze", prompt: "Analyze the Q4 revenue dataset and produce a summary" },
    ],
  },
  executeImmediately: true,
}),
});
const { workflow, taskIds, status }: { workflow: Workflow; taskIds: string[]; status: string } = await response.json();

console.log(`Workflow ${workflow.id}: ${status}`); // "started"
console.log(`Task IDs: ${taskIds.join(", ")}`);

Example response:

{
  "workflow": {
    "id": "wf-7b2e-c4a9",
    "name": "Quick Analysis",
    "status": "active",
    "runNumber": 1
  },
  "taskIds": ["task-e5f1-8d3c"],
  "status": "started"
}

Workflow Statuses

StatusDescription
draftCreated but not yet executed
activeCurrently executing steps
pausedExecution suspended (can resume via execute)
completedAll steps finished successfully
failedOne or more steps failed

Execution State

Workflow execution state is persisted inside the definition field:

  • _state — Used by sequence, parallel, planner-executor, checkpoint, and swarm patterns. Tracks currentStepIndex, per-step states, and overall workflow status.
  • _loopState — Used by loop pattern. Tracks iteration count, per-iteration results, stop reason, and timing.

Both are automatically cleared when a completed or failed workflow is re-executed or edited back to draft.