Skip to content

Tokens

Tokens track execution position in a workflow, enabling parallel execution, visualization, and crash recovery.

Structure

dart
class WorkflowToken {
  final String id;
  final String currentNodeId;      // Current position
  final String? previousNodeId;    // Where it came from
  final bool isActive;             // Is this token still executing?
  final DateTime createdAt;        // When this token was created
  final String? branchId;          // Branch identifier
  final String? parentTokenId;     // For parallel branches
}

Token Lifecycle

1. Creation at Start

When a workflow starts, a single token is created at the start node:

dart
tokens: [
  WorkflowToken(
    id: 'token-1',
    currentNodeId: 'start',
    isActive: true,
  )
]

2. Movement Through Nodes

As the workflow executes, the token moves:

dart
// After validate task
tokens: [
  WorkflowToken(
    id: 'token-1',
    currentNodeId: 'validateTask',
    previousNodeId: 'start',
    isActive: true,
  )
]

3. Splitting at Parallel Gateway

At a parallel (AND) gateway fork, the token splits:

dart
// Before fork
tokens: [
  { id: 'token-1', currentNodeId: 'parallelGateway', isActive: true }
]

// After fork (3 parallel branches)
tokens: [
  { id: 'token-1', currentNodeId: 'parallelGateway', isActive: false },  // Deactivated
  { id: 'token-2', currentNodeId: 'branchA', parentTokenId: 'token-1', branchId: 'a', isActive: true },
  { id: 'token-3', currentNodeId: 'branchB', parentTokenId: 'token-1', branchId: 'b', isActive: true },
  { id: 'token-4', currentNodeId: 'branchC', parentTokenId: 'token-1', branchId: 'c', isActive: true },
]

4. Merging at Join

When all branches reach a join gateway:

dart
// Branches arriving at join
tokens: [
  { id: 'token-2', currentNodeId: 'joinGateway', isActive: false },  // Arrived, waiting
  { id: 'token-3', currentNodeId: 'joinGateway', isActive: false },  // Arrived, waiting
  { id: 'token-4', currentNodeId: 'joinGateway', isActive: false },  // Arrived, now all here
  { id: 'token-5', currentNodeId: 'nextTask', parentTokenId: 'token-1', isActive: true },  // Merged
]

5. Completion

At an end node, the token is deactivated:

dart
tokens: [
  { id: 'token-5', currentNodeId: 'end', isActive: false }
]

Active vs Inactive Tokens

StateMeaning
isActive: trueToken is at a node and ready to execute
isActive: falseToken has completed or is waiting (join)

Only active tokens represent current execution positions.

Token Queries

dart
// Get active tokens
final activeTokens = instance.tokens.where((t) => t.isActive);

// Get tokens at specific node
final tokensAtNode = instance.tokens.where(
  (t) => t.currentNodeId == 'approvalTask'
);

// Get child tokens of a parent
final childTokens = instance.tokens.where(
  (t) => t.parentTokenId == 'token-1'
);

Visualization

Tokens enable real-time workflow visualization:

dart
// In your UI
Widget buildWorkflowVisualization(WorkflowInstance instance) {
  return WorkflowDiagram(
    definition: definition,
    activeNodeIds: instance.tokens
        .where((t) => t.isActive)
        .map((t) => t.currentNodeId)
        .toSet(),
  );
}

Recovery

Tokens enable crash recovery:

dart
// On server restart
final waitingInstances = await storage.instances.findByStatus(
  WorkflowStatus.waitingForSignal,
);

for (final instance in waitingInstances) {
  // Resume from exact token positions
  await engine.resumeWorkflow(instance.id);
}

Next Steps