Skip to content

Workflow

A Workflow is the executable runtime graph. A WorkflowDefinition is the persisted design-time graph. The engine converts definitions into executable workflows by resolving node configurations, conditions, and executors through a WorkflowTypeResolver.

Definition vs Executable

ModelWhere usedContains
WorkflowDefinitionStorage, JSON, editor library, template seedsRaw node/edge definition data suitable for persistence.
WorkflowEngine execution cacheTyped node configs, executable graph helpers, and resolved node processors.

Storage repositories store WorkflowDefinition. The engine executes Workflow.

Loading

Load from storage:

dart
final definition = await engine.storage.workflows.getByCode(
  'approval',
  version: 4,
);

if (definition != null) {
  final workflow = engine.loadWorkflowDefinition(definition);
  print('${workflow.code} v${workflow.version}');
}

Load from JSON:

dart
final workflow = engine.loadWorkflowJSON(json);

Register a code-built workflow:

dart
final workflow = WorkflowBuilder('order', 'Order Workflow')
    .start('start')
    .task('validate', execute: (ctx) async => {'valid': true})
    .end('done')
    .build();

engine.registerWorkflow(workflow);

Workflow Shape

dart
class Workflow {
  final String? id;
  final String code;
  final String name;
  final String? description;
  final int version;
  final bool isActive;
  final List<WorkflowNode> nodes;
  final List<WorkflowEdge> edges;
  final WorkflowTimeouts timeouts;
  final RetryPolicy? defaultRetryPolicy;
  final Map<String, dynamic>? inputSchema;
  final Map<String, dynamic> metadata;
  final List<String> tags;
  final DateTime? createdAt;
  final DateTime? updatedAt;
}

WorkflowBuilder.build() generates an ID as ${code.toLowerCase()}-v${version}. Stored definitions usually carry database-generated IDs.

Nodes

Workflow nodes are typed by NodeType and carry a NodeConfiguration:

dart
switch (node.config) {
  case ServiceTaskNodeConfiguration config:
    print(config.schemaType);
  case UserTaskNodeConfiguration config:
    print(config.title);
  case SignalEventNodeConfiguration config:
    print(config.signalName);
}

The resolver chooses the configuration class from the node type. The schemaType inside task and user-task configs identifies the executor, not the configuration class.

Edges

Edges connect source and target nodes and may include:

  • sourcePortId for output-port routing.
  • condition for gateway decisions.
  • priority for ordered branch evaluation.
  • isDefault for fallback routing.
dart
class WorkflowEdge {
  final String id;
  final String sourceNodeId;
  final String targetNodeId;
  final String? sourcePortId;
  final ConditionExecutor? condition;
  final int priority;
  final bool isDefault;
}

Registration

The engine has an in-memory executable workflow cache. It is populated by:

  • registerWorkflow(workflow).
  • loadWorkflowDefinition(definition).
  • loadWorkflowJSON(json).
  • startWorkflow(...), when the requested workflow is not already cached and can be loaded from storage.

Registration validates the workflow and resolves node processors.

Versioning

Start latest active version by code:

dart
await engine.startWorkflow(
  workflowCode: 'approval',
  input: input,
);

Start a specific version:

dart
await engine.startWorkflow(
  workflowCode: 'approval',
  version: 4,
  input: input,
);

Start by stored definition ID:

dart
await engine.startWorkflow(
  workflowId: workflowDefinitionId,
  input: input,
);

Running instances keep their workflowId, status, tokens, input, output, and metadata. Updating a definition does not rewrite already-running instances.

Serialization

Use WorkflowDefinition for persisted JSON. If you have a Workflow and want to persist it:

dart
await engine.saveWorkflow(workflow);

If you have raw JSON:

dart
final workflow = engine.loadWorkflowJSON(json);

Avoid direct Workflow.fromJson() unless you explicitly run it inside a WorkflowTypeResolver scope. WorkflowEngine loading methods handle the resolver scope for you.

Validation

dart
final errors = workflow.validate();
if (errors.isNotEmpty) {
  throw WorkflowValidationException(errors);
}

The engine validates automatically when loading or registering workflows.

See Also