A workflow chains agent calls, tool calls and human confirmations into one graph: the definition is stored as node and edge JSON, the graph is validated before a run, and each node leaves its own record. One key fact: execution is sequential, not parallel — a single scoped DbContext is shared on purpose, trading parallelism for simplicity and consistency.
Definition, validation, run and node records are all persisted; the run snapshots the graph so later edits cannot affect an in-flight execution.
Definition model
Field
Notes
Name · Description · SortOrder · IsEnabled
Metadata and list ordering
Nodes · Edges
Stored as JSON strings; node type constants: start / agent / condition / loop / parallel / merge / tool / human / subworkflow / end
InputSchema · Variables
Input contract and variable declarations
Version
Starts at 1 and increments on every update
Node Config
Per-node JSON configuration, see the executor table
Endpoints
Endpoint
Purpose
GET /api/workflows · GET /api/workflows/{id}
List / detail
POST /api/workflows · PUT /api/workflows/{id} · DELETE /api/workflows/{id}
Create / update / delete (nodes and edges are serialised to JSON and the version increments)
POST /api/workflows/{id}/duplicate
Copy a definition with a localised copy suffix
GET /api/workflows/{id}/validate
Validate the graph without running it
POST /api/workflows/{id}/run
Run once and return the result
POST /api/workflows/{id}/run/stream
Run with an SSE stream
POST /api/workflows/{id}/run/topic/{topicId}/stream
Streaming run that also writes the process into a chat topic
GET /api/workflows/{id}/runs · GET /api/workflows/runs/{runId}
Run history / one run with its node records
POST /api/workflows/runs/{runId}/approve
Human review callback: { nodeId?, approve }
Graph validation rules
At least one start and one end node.
Both endpoints of every edge must exist.
No orphan nodes other than start (everything must be reachable).
agent nodes must carry an AgentId (a project agent preset).
Validation runs again before execution, so "saved fine but fails validation at run time" usually means the definition changed underneath or an edge was removed.
Node executors
Node
Config and behaviour
start
Stores the raw input as start.input; if the input is a JSON object, each field also becomes a variable
agent
instruction / modelId / toolNames / mcpServerIds / toolApprovals / maxIterations / maxToolCallsPerTurn; upstream output is injected as input, falling back to the run input; SessionId = workflow-{runId}-{nodeId}; executed through the agent loop and writes thinking and toolCalls; usage source workflow
condition
{ branches: [{ handle, expression }], defaultHandle }; the first matching branch determines the outgoing handle
loop
{ maxIterations, exitCondition }; the first pass enters body, the exit condition or the iteration cap leaves through exit
tool
{ toolName, argumentsTemplate }; calls a built-in or MCP tool without an LLM round trip
{ subWorkflowId, inputTemplate }; recursion depth capped at 5
parallel / merge
A node with multiple outgoing edges fans out automatically; branch results are collected and merged at the common successor
end
Renders outputTemplate when configured, otherwise takes the first non-empty upstream output, falling back to the run input
"Parallel" here is structurally parallel but sequentially executed: branches run one after another and then merge, sharing one scoped DbContext. That explains two behaviours: agent calls in branches are serial, and a failure in any branch aborts the whole run.
Tool activity inside an agent node (the frontend can answer questions, plans or approvals)
run_result
Final result payload
The full protocol (three frame shapes, unknown event handling) is in SSE event protocol; agent nodes still emit the [ERROR] fallback frame.
Relation to the task board
Kanban run records support Kind = Agent | Workflow with Trigger = Todo | Comment | Manual.
So a card can point at a workflow and the executor runs it once the card reaches the todo column, leaving process and result in the run record and comments (see Task board & automation).
Agent nodes inside a workflow do not reuse the kanban run record; they write their own node records.
Practice notes
Always give loops a maxIterations: the 20-visit cap per node is the last line of defence and it fails the run outright.
Agent session ids include run and node ids, so every run starts clean; share context across nodes through variables, not chat history.
tool nodes skip the LLM: ideal for fixed actions (run a command, fetch data) — cheaper and more predictable than letting a model decide.
Wire /validate into the editor flow; it catches the most common "saved but fails" cases such as a missing end node or an agent node without an agent.
Definition edits never affect an in-flight run: compare the run's GraphSnapshot when results look inconsistent.