How Condition Handlers Evaluate Workflow Branching Logic in SimStudio AI

Condition handlers in SimStudio AI evaluate workflow branching logic by parsing JSON condition definitions, executing sandboxed JavaScript expressions via the function_execute tool with a 5-second timeout, and routing execution to the first matching path based on boolean results.

The condition block serves as the decision-making core of SimStudio AI workflows. According to the simstudioai/sim source code, the condition handler (apps/sim/executor/handlers/condition/condition-handler.ts) transforms declarative user input into isolated code evaluations that determine execution flow. This process involves context aggregation, secure script execution, and precise edge resolution to route data between workflow blocks.

Parsing User-Defined Conditions

The evaluation process begins with ConditionBlockHandler.parseConditions, which normalizes raw input into a structured array of condition objects.

This method accepts either a JSON string or an already-parsed array and validates each entry against the { id, title, value } interface. If the input contains malformed data, the handler surfaces a clear log message and throws an exception, halting workflow execution before any evaluation occurs.

The parsed conditions represent the decision branches—each containing a unique identifier, human-readable title, and the JavaScript expression that determines if that path should be taken.

Building the Evaluation Context

Before expressions can execute, the handler must establish what variables the code can reference. The ConditionBlockHandler.buildEvaluationContext method merges the output from the source block (the block feeding into the condition) into a plain JavaScript object.

This context object becomes the variable scope for condition expressions. When users write expressions like <ticket.priority> === 'high', the angle-bracket notation resolves against this context, allowing conditions to reference upstream data without direct database access or unsafe scope leakage.

Executing Sandboxed Condition Expressions

For each condition, the handler calls evaluateConditionExpression, which wraps the user code in a safety harness:

const context = { …evalContext };
return Boolean(<condition>);

The actual execution occurs inside the function_execute tool, invoked via executeTool('function_execute', …). This approach provides critical isolation:

  • Timeout protection: Execution halts after CONDITION_TIMEOUT_MS (5 seconds)
  • Variable isolation: Only the built context and workflow/environment variables are accessible
  • Result normalization: The tool returns { success, output?.result }, which the handler coerces to a boolean

If the tool reports failure or throws an error, the handler logs the specific failure and aborts the entire workflow, preventing partial execution of invalid branches.

Routing to the Next Block

Once expressions are evaluated, ConditionBlockHandler.evaluateConditions determines the execution path. The algorithm follows strict precedence rules:

  1. Else handling: If a condition title matches the special else title, its edge is selected immediately when no prior condition matches
  2. First-match-wins: For standard conditions, the handler iterates sequentially and selects the first condition returning true
  3. Edge resolution: The handler calls findConnectionForCondition to locate the outgoing connection whose sourceHandle matches condition-{conditionId} (handled via utilities in apps/sim/lib/workflows/condition-ids.ts)

When a match is found, the handler returns the target block's ID, type, and title. If no conditions match and no else clause exists, selectedPath returns null, effectively terminating that branch of the workflow.

Recording Decisions and Returning Output

The handler maintains audit trails by storing the selected condition ID in ctx.decisions.condition under a key derived from either the virtual or physical block ID. This storage enables downstream blocks to reference which branch was taken using the <condition.selectedOption> syntax.

The final output merges the source block's data (with internal metadata stripped) alongside condition-specific fields:

{
  conditionResult: true,
  selectedPath: { blockId, blockType, blockTitle },
  selectedOption: condition.id,
}

When no match occurs, conditionResult is false and both selectedPath and selectedOption are null, signaling to subsequent blocks that no branching condition was satisfied.

Practical Implementation Example

Define a condition block in your workflow JSON:

{
  "id": "block-3",
  "metadata": { "id": "condition" },
  "inputs": {
    "conditions": [
      { "id": "c1", "title": "High priority", "value": "<ticket.priority> === 'high'" },
      { "id": "c2", "title": "Urgent keyword", "value": "<ticket.subject>.toLowerCase().includes('urgent')" },
      { "id": "c3", "title": "Else", "value": "true" }
    ]
  }
}

Configure the corresponding connections to route execution:

[
  { "source": "block-2", "target": "block-3", "sourceHandle": "output" },
  { "source": "block-3", "target": "block-4", "sourceHandle": "condition-c1" },
  { "source": "block-3", "target": "block-5", "sourceHandle": "condition-c2" },
  { "source": "block-3", "target": "block-6", "sourceHandle": "condition-c3" }
]

Within the executor, the handler selects the appropriate edge:

const { selectedConnection, selectedCondition } = await handler.evaluateConditions(
  conditions,
  outgoingConnections,
  evalContext,
  ctx,
  block.id
);
// selectedConnection.target now references block-4, block-5, or block-6

Summary

  • Condition parsing converts raw JSON into validated { id, title, value } objects via parseConditions
  • Context building aggregates source block output into a sandboxed variable scope using buildEvaluationContext
  • Sandboxed execution runs JavaScript expressions through the function_execute tool with a 5-second timeout and boolean coercion
  • Branch selection follows first-match-wins logic, with special handling for else clauses and precise edge mapping via condition-{id} handles
  • Audit trails store decisions in ctx.decisions.condition, enabling downstream reference via selectedOption
  • Type-safe outputs return conditionResult, selectedPath, and selectedOption to inform subsequent workflow steps

Frequently Asked Questions

How does the condition handler isolate JavaScript execution?

The handler delegates expression evaluation to the function_execute tool, which executes code in a sandboxed environment with restricted variable access and a hard 5-second timeout (CONDITION_TIMEOUT_MS). This prevents user-defined conditions from accessing unauthorized system resources or entering infinite loops.

What happens if no conditions evaluate to true?

If no standard conditions match and no else clause is defined, evaluateConditions returns conditionResult: false with selectedPath and selectedOption set to null. The workflow execution halts for that branch unless subsequent blocks handle null paths explicitly.

How does SimStudio AI handle edge cases when duplicating condition blocks?

The apps/sim/lib/workflows/condition-ids.ts utility ensures that when blocks are duplicated, condition IDs and edge handles (condition-{conditionId}) are remapped consistently. This prevents routing conflicts and ensures the handler in condition-handler.ts resolves the correct sourceHandle even in copied workflows.

Can condition expressions access data from multiple previous blocks?

Yes. The buildEvaluationContext method aggregates output from the immediate source block, but the context can be extended to include workflow-wide variables and environment data. However, expressions can only reference data explicitly included in the evaluation context passed to evaluateConditionExpression.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →