OptionalagentSettings for an Agent step. The agent itself is Task.AgentID.
OptionalexternalSettings for a step completed by a system outside MemberJunction.
OptionalforThe loop definition for a ForEach step. Mirrors ForEachOperation in @memberjunction/ai-core-plus.
OptionalhumanSettings for a step a person completes. The assignee is Task.UserID.
OptionalinputHow this step's inputs are built from the workflow payload, as a JSON object mapping the step's parameter names to payload paths.
Evaluated when the step is dispatched, never when the workflow is submitted: a step's input routinely depends on what an earlier step produced, which does not exist yet at submission.
OptionallayoutWhere this step sat on the canvas when a person drew it.
Only ever the author's own arrangement — never a computed one. A graph produced by an agent or a remote caller has no geometry, and its positions are derived at render time from the graph's shape. Persisting a derived layout would freeze one rendering of a graph that can still change, and the stored coordinates would quietly go stale.
So the rule for anything drawing a run is: use this when it is present, compute a layout when it is not. That is what makes a workflow someone laid out by hand run — and appear in history — in the shape they drew, while a machine-authored graph still renders legibly.
OptionaloutputHow this step's results are written back into the workflow payload, as a JSON object mapping result field names to payload paths.
This is the only way a later step — or a branch condition — can see what this step produced.
A workflow that branches on payload.stockPrice gets that value because some earlier step
mapped it there.
OptionalpolicyTimeout, retries, and what failure means for the rest of the workflow.
OptionalpromptSettings for a Prompt step. The prompt itself is Task.PromptID.
OptionalruntimeWhat actually happened when this step ran — written by the dispatcher, never by an author.
Everything else in this bag is a step's definition; this is its history. It lives here rather than in columns of its own for the same reason as the rest: nothing in it is ever a SQL predicate, and a column per runtime artefact would be a migration every time a new step kind produced one.
OptionalsequenceWhere this step sits in the graph's own order — its rank in a topological sort of the dependency edges, assigned once at submission.
Why a stored rank rather than an ordering rule. Task has no sequence column, and every
consumer that lists a graph's steps was therefore falling back to __mj_CreatedAt — which is
the COMPILER's walk order, related to neither the order someone drew the steps in nor the
order they run in. A graph that had not started yet listed its steps essentially at random:
step 2(b), step 3, step 1, step 2(a), with step 3 above the steps it depends on.
A graph's edges already define a partial order, and that order is knowable before anything runs — which is exactly when it is needed, since there are no timestamps to sort by yet. Steps that share a rank are genuinely concurrent, and consumers break that tie with the real start time, so the rule reads "drawn order, then what actually happened".
Assigned at submission and never rewritten: it describes the graph's shape, which does not change once compiled.
OptionalwhileThe loop definition for a While step. Mirrors WhileOperation in @memberjunction/ai-core-plus.
Everything a workflow step needs that has no column of its own — stored on Task.Configuration.
A workflow compiles to a task graph and runs on the dispatcher, so each Task row IS a workflow step. Three parts of a step have their own columns because SQL needs them:
StepType(the discriminator the claiming query routes on) and theAgentID/ActionID/PromptID/UserIDforeign keys. Everything else lives here.Why a JSON bag rather than more columns. None of the fields below is ever a SQL predicate — the dispatcher loads the row and reads them in TypeScript. Made into columns they would be eight more migrations' worth of surface for no query benefit, and every new step kind or policy knob would need another one. As a typed bag, growth is a change to this file plus CodeGen.
Which member is populated follows
Task.StepType. TypeScript cannot discriminate on a sibling column, so the kind-specific members are individually optional rather than a discriminated union. Read them through theStepTypeyou already have; do not infer the kind by probing for whichever member happens to be present, which is the exact mistake that used to turn every unrecognized step into a person's approval task.