Skip to content

Visual Flow Editor ​

The Visual Flow Editor lets you design complex multi-agent workflows using a node-based interface. Flows automate tasks by connecting triggers, agents, conditions, and actions into executable pipelines.

What is a Flow? ​

A Flow is an automated workflow that:

  • Triggers based on events, schedules, or manual activation
  • Executes one or more agents in sequence or parallel
  • Processes data between steps with conditions and transformations
  • Outputs results to tickets, files, or notifications

Flows enable powerful automation like:

  • Daily research summaries
  • Automated code reviews on PRs
  • Multi-stage document processing
  • Ticket triage and assignment

Flow Architecture ​

Flow Definition ​

typescript
interface Flow {
  id: string;           // Unique identifier (e.g., "flow-xxx")
  name: string;         // Display name
  description: string;  // What the flow does
  nodes: FlowNodeData[];  // All nodes in the flow
  edges: FlowEdge[];      // Connections between nodes
  variables: Record<string, JSONSchema>;  // Flow-level variables
  labelIds: string[];     // Labels for organization
  source?: 'user' | 'community';  // Origin of the flow
  enabled?: boolean;      // Whether automatic triggers are active
  maxIterations?: number; // Max times a node can execute in loops (default: 5)
  createdAt: Date;
  updatedAt: Date;
}

Flow Storage ​

Flows are persisted as JSON files:

  • User flows: .sciorex/flows/{flow-id}.json
  • Built-in flows: resources/flows/ (read-only)

Flow Editor Interface ​

Flow Editor

The editor provides:

  • Canvas: Drag-and-drop node placement with zoom/pan
  • Minimap: Overview navigation for large flows
  • Sidebar: Node palette and property editor
  • Toolbar: Save, run, validate, and execution controls
  • Alignment Guides: Smart snapping for node alignment

Node Types ​

Trigger Node ​

Every flow starts with a trigger that initiates execution.

typescript
interface TriggerNodeData {
  type: 'trigger';
  triggerType: 'manual' | 'ticket-created' | 'ticket-updated' | 'schedule';
  triggerConfig: {
    cron?: string;           // For schedule triggers
    ticketFilter?: object;   // For ticket triggers
    inputSchema?: JSONSchema; // For manual triggers: defines expected input fields
  };
}
Trigger TypeDescriptionConfiguration
manualRun on-demand via UI or APINone required
scheduleCron-based schedulingcron: "0 9 * * *" (daily at 9am)
ticket-createdWhen a ticket is createdOptional ticket filter
ticket-updatedWhen a ticket is updatedOptional ticket filter

Agent Node ​

Executes an AI agent with input/output mapping.

typescript
interface AgentNodeData {
  type: 'agent';
  agentId: string;
  inputMapping: Record<string, string>;   // Map context to input
  outputMapping: Record<string, string>;  // Map output to context
  outputSchema?: JSONSchema;              // Override agent's output schema
  worktreeMode?: 'none' | 'new' | 'inherit';  // Git worktree isolation
  inheritWorktreeFromNodeId?: string;     // Node to inherit worktree from
  worktreeConfig?: {                      // Config for 'new' worktree mode
    label?: string;
    baseBranch?: string;
    autoMergeOnSuccess?: boolean;
    cleanupOnComplete?: boolean;
  };
  resumeFromNodeId?: string;              // Resume a previous session
  reuseSessionOnLoop?: boolean;           // Reuse session in loop iterations
}

Input Mapping Examples:

javascript
{
  "query": "$.trigger.searchTerm",
  "context": "$.nodes.previousAgent.output"
}

Output Mapping Examples:

javascript
{
  "$.results": "summary",
  "$.metadata.sources": "sources"
}

Condition Node ​

Routes execution based on context data evaluation.

typescript
interface ConditionNodeData {
  type: 'condition';
  conditions: FlowCondition[];
  logicOperator: 'and' | 'or' | 'xor';  // How to combine conditions
  negate?: boolean;           // Invert the result (for NAND/NOR/XNOR logic)
  targetNodeId?: string;      // Node to go to when conditions match
  defaultTargetNodeId?: string; // Node to go to when conditions don't match
}

interface FlowCondition {
  path: string;      // JSONPath to value (e.g., "$.results.score")
  operator: string;  // eq, ne, gt, lt, gte, lte, contains, matches
  value: unknown;    // Value to compare against
}

Operators:

OperatorDescriptionExample
eqEqualsscore eq 100
ne / neqNot equalsstatus ne "failed"
gtGreater thancount gt 5
gteGreater or equalpriority gte 3
ltLess thanage lt 30
lteLess or equalversion lte 2
containsString containstitle contains "urgent"
matchesRegex matchemail matches ".*@company.com"

Conditions evaluate to:

  • Match edge: Follows when conditions are true
  • Default edge: Follows when conditions are false

Wait Node ​

Pauses execution for human input or approval.

typescript
interface WaitNodeData {
  type: 'wait';
  waitType: 'approval' | 'input' | 'timeout';
  prompt?: string;           // Message to display
  timeoutMs?: number;        // Auto-continue timeout
  inputSchema?: JSONSchema;  // Schema for input collection
}
Wait TypeDescription
approvalPause until user approves/rejects
inputPause until user provides input
timeoutPause for specified duration

Parallel Node ​

Splits execution into multiple concurrent branches.

typescript
interface ParallelNodeData {
  type: 'parallel';
  branches: string[];  // Node IDs to execute in parallel
}

Merge Node ​

Joins parallel branches back together.

typescript
interface MergeNodeData {
  type: 'merge';
  mergeStrategy: 'all' | 'any' | 'first';
  sourceNodes: string[];  // Node IDs to wait for
}
StrategyDescription
allWait for all branches to complete
anyContinue when any branch completes
firstContinue when first branch completes

Transform Node ​

Transforms data in the execution context.

typescript
interface TransformNodeData {
  type: 'transform';
  transformType: 'jq' | 'template' | 'script';
  expression: string;  // Transform expression
}
TransformDescriptionExample
jqJQ query syntax.items | map(.name)
templateTemplate string{{nodes.agent.output.summary}}
scriptJavaScript codereturn data.map(x => x * 2)

Ticket Action Node ​

Performs actions on tickets.

typescript
interface TicketActionNodeData {
  type: 'ticket_action';
  action: 'create' | 'update_status' | 'add_comment' | 'add_label' | 'link_resource' | 'unlink_resource' | 'link_current_flow_run';
  actionParams: Record<string, unknown>;
}

End Node ​

Marks the end of a flow execution path.

typescript
interface EndNodeData {
  type: 'end';
  endType?: 'success' | 'failure' | 'cancelled';
}

Flow Edges ​

Edges connect nodes and define execution order:

typescript
interface FlowEdge {
  id: string;
  source: string;       // Source node ID
  target: string;       // Target node ID
  sourceHandle?: string; // Output handle (for conditions)
  targetHandle?: string; // Input handle
  label?: string;        // Edge label
}

For condition nodes, edges use handles:

  • match - Condition is true
  • default - Condition is false

Flow Execution ​

Execution States ​

typescript
enum FlowExecutionStatus {
  Pending = 'pending',
  Running = 'running',
  Paused = 'paused',
  Completed = 'completed',
  Failed = 'failed',
  Cancelled = 'cancelled',
}

enum NodeExecutionStatus {
  Pending = 'pending',
  Running = 'running',
  Completed = 'completed',
  Failed = 'failed',
  Skipped = 'skipped',
  Waiting = 'waiting',
}

Execution Context ​

Each execution maintains a shared context:

typescript
interface FlowExecution {
  id: string;
  flowId: string;
  status: FlowExecutionStatus;
  context: Record<string, unknown>;     // Shared data
  nodeResults: Map<string, NodeExecutionResult>;
  currentNodeIds: string[];             // Currently executing
  nodeExecutionCounts: Record<string, number>; // Loop iteration tracking
  output?: unknown;                     // Final flow output
  archived?: boolean;                   // Whether the execution is archived
  pausedAt?: Date;                      // When execution was paused
  startedAt: Date;
  completedAt?: Date;
  error?: string;
}

Context Paths ​

Access data using JSONPath-like syntax:

javascript
// Trigger input
{{trigger.input}}
{{$.trigger.searchQuery}}

// Previous node output
{{previousNode.output}}

// Specific node output
{{nodes.paperFinder.output}}
{{$.nodes.summarizer.output.summary}}

// Flow metadata
{{$.flowMeta.flowId}}
{{$.flowMeta.flowName}}

Execution Algorithm ​

The flow executor uses topological ordering:

  1. Start: Create execution context from trigger
  2. Find Ready Nodes: Nodes whose dependencies are complete
  3. Execute in Parallel: Run ready nodes concurrently
  4. Update Context: Store node outputs
  5. Repeat: Until all paths reach end nodes
typescript
async executeFlow(executionId: string): Promise<void> {
  while (true) {
    // Check cancellation/pause
    if (state.cancelRequested) return handleCancellation();
    if (state.pauseRequested) return handlePause();
    
    // Find and execute ready nodes
    const executableNodes = findExecutableNodes(state);
    if (executableNodes.length === 0 && runningNodes.size === 0) {
      return isFlowComplete(state) ? handleCompletion() : handleStuck();
    }
    
    // Execute nodes in parallel
    await Promise.race(executableNodes.map(nodeId => executeNode(state, nodeId)));
  }
}

Creating a Flow ​

Step 1: Add a Trigger ​

  1. Open the Flow Editor
  2. Drag a Trigger node onto the canvas
  3. Configure the trigger type and settings

Step 2: Add Processing Nodes ​

Connect nodes to build your workflow:

  1. Drag nodes from the sidebar
  2. Connect nodes by dragging from output to input handles
  3. Configure each node's properties

Step 3: Configure Data Flow ​

Use template expressions to pass data:

javascript
// Reference trigger input
{{trigger.input}}

// Reference previous node output
{{previousNode.output}}

// Reference specific node by name
{{nodes.paperFinder.output}}

// Extract specific fields
{{nodes.summarizer.output.summary}}

Step 4: Save and Run ​

  • Click Save to store the flow
  • Click Run for manual execution
  • View execution status in real-time

Flow Controls ​

During execution, you can:

ActionDescription
PausePause at next safe point
ResumeContinue paused execution
CancelStop execution entirely
Inject DataProvide input to waiting nodes

Example Flows ​

Flow execution in progress

Research Pipeline ​

Trigger (Daily @ 9am)
    ↓
Paper Finder Agent
    ↓
Summarizer Agent
    ↓
Create Ticket (sciorex_create_ticket)

Use Case: Daily digest of new research papers

Code Review with Approval ​

Trigger (ticket-created, labels: "review")
    ↓
Code Analyzer Agent
    ↓
Security Scanner Agent
    ↓
Wait (Human Approval)
    ↓
  ┌─────┴─────┐
  ↓           ↓
Approve     Reject
  ↓           ↓
Merge PR   Request Changes

Use Case: Automated code review with human oversight

Parallel Processing ​

Trigger (Manual)
    ↓
┌───────────────────┐
│    Parallel       │
│  ┌─────┐ ┌─────┐  │
│  │ A1  │ │ A2  │  │
│  └─────┘ └─────┘  │
└───────────────────┘
    ↓
Merge (strategy: all)
    ↓
Combine Results
    ↓
Output

Use Case: Process data with multiple agents concurrently

Conditional Routing ​

Trigger (ticket-updated)
    ↓
Triage Agent
    ↓
Condition (priority > 3)
    ├─ match → Urgent Handler Agent
    └─ default → Standard Handler Agent
    ↓
Update Ticket Status

Use Case: Route tickets based on priority

Flow Validation ​

Flows are validated before execution:

CheckDescription
Has TriggerAt least one trigger node required
No OrphansAll nodes must be connected
Unique IDsNode IDs must be unique
Valid EdgesEdges must reference existing nodes
No CyclesCycles trigger a warning (feedback loops must be intentional)
Valid AgentsAgent nodes must reference existing agents

Flow Events ​

The flow engine emits events for monitoring:

EventDescription
flow:startedFlow execution began
flow:completedFlow finished successfully
flow:failedFlow ended with error
flow:pausedFlow paused
flow:resumedFlow resumed
node:startedNode began executing
node:completedNode finished successfully
node:failedNode ended with error
node:waitingNode waiting for input

Debug Mode ​

Debug Mode lets you step through a flow node by node to inspect and modify data at each stage.

Debug Actions ​

ActionDescription
StepExecute the next node and pause
ContinueRun until the next breakpoint or completion
StopCancel the execution
SkipSkip a node and provide mock output
Modify InputChange a node's resolved inputs before execution
Modify OutputChange a node's output after execution

Breakpoints ​

Set breakpoints on nodes to pause execution at specific points. Breakpoints can trigger before or after node execution, letting you inspect resolved inputs and actual outputs.

typescript
interface DebugBreakpoint {
  nodeId: string;
  type: 'before' | 'after';
}

Error Recovery ​

When a node fails during execution (outside Debug Mode), the flow pauses and presents recovery options:

ActionDescription
RetryRe-execute the failed node
SkipSkip the node and provide mock output to continue the flow
StopCancel the entire execution
typescript
interface NodeErrorState {
  nodeId: string;
  error: string;
  timestamp: Date;
  recoveryAction?: 'skip' | 'retry' | 'stop';
  mockOutput?: unknown;  // Used when skipping a failed node
}

Worktree Isolation ​

Agent nodes can run in isolated Git worktrees, keeping changes separate from the main branch:

Worktree ModeDescription
noneNo worktree (default)
newCreate a new worktree for this node
inheritReuse a worktree from another node

When using new mode, you can configure:

  • Base branch for the worktree
  • Auto-merge on success to merge changes back automatically
  • Cleanup on complete to remove the worktree after use

Use inherit with inheritWorktreeFromNodeId to share a worktree between sequential nodes (e.g., Plan → Implement → Review all working on the same branch).

Best Practices ​

Start simple

Begin with linear flows before adding conditions and parallelism.

Use descriptive names

Name your nodes clearly (e.g., "Summarize Papers" not "Agent 1").

Handle errors

Add error handling nodes for critical workflows. Consider using condition nodes to check for errors.

Test incrementally

Run flows after adding each node to catch issues early.

Avoid infinite loops

Be careful with cycles - ensure they have proper exit conditions.

Integration with Tickets ​

Flows integrate deeply with the ticketing system:

  • Trigger on ticket events: Start flows when tickets are created/updated
  • Create tickets from flows: Use Ticket Action nodes
  • Update ticket status: Modify tickets during execution
  • Link sessions: Flow agent sessions can be linked to tickets

API Operations ​

Create Flow ​

typescript
const flow = await flowService.createFlow({
  name: 'My Workflow',
  description: 'Automated pipeline',
  nodes: [...],
  edges: [...]
});

Execute Flow ​

typescript
const execution = await flowExecutor.startFlow(flow, {
  searchQuery: 'machine learning'  // Initial context
});

Control Execution ​

typescript
flowExecutor.pauseExecution(executionId);
flowExecutor.resumeExecution(executionId);
flowExecutor.cancelExecution(executionId);

Inject Data ​

typescript
await flowExecutor.injectData(executionId, nodeId, {
  userInput: 'approved'
});

Next Steps ​

Sciorex is proprietary software.