Skip to content

Board Engine

WebSocket Flow Execution - Real-time flow execution engine with pause, resume, and input capabilities.


The Board engine (backend/ion/board/) provides real-time flow execution through WebSocket communication. It enables:

  • Live flow execution monitoring
  • Pause/Resume/Stop controls
  • External input during execution
  • Progress and error streaming

board/
├── engine.go # ExecutionEngine implementation
├── hub.go # WebSocket hub management
├── server.go # WebSocket server handling
├── types.go # Types and interfaces
├── company_dev_flow.go # Company development mode
├── company_live_flow.go# Company production mode
├── account_dev_flow.go # Account development mode
├── account_live_flow.go# Account production mode
├── company_session.go # Company session management
└── account_session.go # Account session management

engine, err := board.NewExecutionEngine(
sessionID, // Unique session identifier
executor, // Executor implementation
context, // Execution context
messageSender, // WebSocket message callback
)
MethodDescription
Start()Begin execution in goroutine
Pause()Pause execution
Resume()Resume from pause
Stop()Stop execution
// Start execution
err := engine.Start()
// Pause
err := engine.Pause()
// Resume
err := engine.Resume()
// Stop
engine.Stop()

stateDiagram-v2
[*] --> pending: NewEngine
pending --> running: Start
running --> paused: Pause
paused --> running: Resume
running --> waiting_input: WaitForInput
waiting_input --> running: Input received
running --> completed: Success
running --> error: Error
running --> stopped: Stop
paused --> stopped: Stop
waiting_input --> stopped: Stop
StatusDescription
pendingEngine created, waiting to start
runningActively executing
pausedExecution paused
waiting_inputWaiting for external input
stoppedManually stopped
completedSuccessfully finished
errorFailed with error

TypeDescription
readyEngine ready to execute
logLog message
progressExecution progress update
errorError occurred
waitingWaiting for input
completeExecution completed
status_updateStatus changed
CommandDescription
startStart execution
pausePause execution
resumeResume execution
stopStop execution
inputProvide input data

{
"command": "start",
"payload": {
"flowId": "flow-123",
"nodeId": "n_1"
}
}
{
"command": "progress",
"payload": {
"node_id": "n_1",
"status": "completed",
"data": {...}
}
}

Custom executors implement this interface:

type Executor interface {
// GetType returns executor type (e.g., "flow", "integration")
GetType() string
// Execute performs the main execution logic
Execute(ctx ExecutionContext, control ExecutionControl) (*ExecutionResult, error)
// Cleanup performs cleanup after execution
Cleanup()
}

Available to executors during execution:

type ExecutionControl interface {
// Pause handling
CheckPause() error
// Input handling
WaitForInput(timeout time.Duration, payload any) (any, error)
// Message sending
SendProgress(payload any)
SendLog(message string)
SendError(message string, errors ...string)
// Context access
GetContext() context.Context
GetData() any
SetData(data any)
// Unit tracking
ConsumeUnits(units uint64)
GetUnitsConsumed() uint64
// State access
GetExecutionState() *ExecutionState
GetExecutionContext() *ExecutionContext
}

type FlowExecutor struct {
flowId string
flow *models.Flow
startNode string
}
func (e *FlowExecutor) GetType() string {
return "company_dev_flow"
}
func (e *FlowExecutor) Execute(ctx ExecutionContext, control ExecutionControl) (*ExecutionResult, error) {
// Load flow
err := flow.LoadFlow(e.flowId, *e.flow)
if err != nil {
return nil, err
}
// Run initial node
response, err := flow.RunNode(e.flowId, e.startNode, "output")
if err != nil {
return nil, err
}
// Execution loop
for response != nil && response.Queue != nil {
// Check for pause
if err := control.CheckPause(); err != nil {
return nil, err
}
// Send progress
control.SendProgress(map[string]any{
"node_id": response.Node.ID,
"type": response.Node.Type,
})
// Process node...
outputs := processNode(response)
// Push results
result, _ := flow.PushNode(e.flowId, outputs, "ok", status.Success, "")
// Continue
response, _ = flow.ContinueNode(e.flowId)
}
return &ExecutionResult{
Status: StatusCompleted,
Message: "Flow completed",
}, nil
}
func (e *FlowExecutor) Cleanup() {
// Clean up flow workspace
}

The Hub manages active WebSocket sessions:

// Setup hub
board.SetupHub()
// Register session
board.CHub.Register <- session
// Unregister session
board.CHub.Unregister <- session
// Broadcast to all
board.CHub.Broadcast <- message
// Shutdown
board.CHub.Shutdown()

Wait for external input during execution:

func (e *FlowExecutor) Execute(...) (*ExecutionResult, error) {
// ...
if needsInput {
// Wait for input (0 = indefinite)
input, err := control.WaitForInput(0, map[string]any{
"message": "Please provide approval",
"fields": []string{"approved", "reason"},
})
if err != nil {
return nil, err
}
// Use input
approved := input.(map[string]any)["approved"].(bool)
}
// ...
}