Trovella Wiki

Branching Conditions

How branching conditions alter plan execution flow through safe expression evaluation and typed actions (skip_to, add_steps, fail, continue).

Branching conditions let research plans adapt their execution flow based on step results. After each step completes, submit_step_result evaluates any conditions attached to that step and executes the resulting action. This enables plans to skip unnecessary steps when confidence is high, fail early when data quality is low, or dynamically add steps when deeper investigation is needed.

How Branching Conditions Are Created

Branching conditions are defined at plan creation time via the branchingConditions parameter of create_research_plan. Each condition specifies:

FieldTypePurpose
afterStepOrdernumberWhich step this condition applies to (1-based order)
conditionExpressionstringThe expression to evaluate against step results
ifTrueActionstringThe action to take when the condition is true
actionParamsRecord<string, unknown>Optional parameters for the action (e.g., target step ID)

During plan creation, afterStepOrder values are resolved to actual step IDs. If a condition references a step order beyond the plan's step count, plan creation fails with INVALID_STEP_REFERENCE.

The conditions are stored in the plan_branching_condition table, linked to both the plan and the specific step they follow. See Data & Storage -- Schema Design for the table definition.

Expression Evaluation

The expression evaluator in packages/mcp/src/plan-engine/branching.ts uses safe string parsing -- no eval(), no Function() constructor. It supports a limited grammar: a left-side dot-path, a comparison operator, and a right-side literal value.

Supported Operators

The evaluator recognizes six comparison operators, checked in this order:

OperatorBehavior
===Strict equality (type-aware)
!==Strict inequality
>=Greater than or equal (numeric)
<=Less than or equal (numeric)
>Greater than (numeric)
<Less than (numeric)

Operator precedence matters because the evaluator finds the first matching operator by scanning the expression string. >= and <= are checked before > and < to avoid partial matches.

Left-side: Dot-path resolution

The left side of the expression is a dot-separated path into the context object. The resolvePath function walks the path segments, returning undefined if any segment is missing:

"confidence"              --> context.confidence
"result.hasData"          --> context.result.hasData
"result.metrics.accuracy" --> context.result.metrics.accuracy

Right-side: Literal parsing

The right side is parsed into a typed JavaScript value:

Raw stringParsed as
trueboolean true
falseboolean false
nullnull
"completed" or 'completed'string (quotes stripped)
0.8number
Anything elseLeft as raw string

Example Expressions

confidence > 0.8              -- numeric comparison against step confidence
result.hasData === true       -- boolean check on nested result field
status === "completed"        -- string equality
result.metrics.accuracy > 0.9 -- deeply nested numeric check
status !== "failed"           -- negative string comparison
score >= 80                   -- numeric threshold

Safety guarantees

The evaluator returns false for any expression it cannot parse. Missing dot-path segments return undefined, and comparisons involving undefined evaluate to false. There is no way to execute arbitrary code -- the evaluator only does property lookup and comparison.

Branch Actions

When a condition evaluates to true, its ifTrueAction string is resolved into a typed BranchAction by resolveBranchAction:

skip_to

Skips all pending steps between the current step and the target step. The target step ID is provided in actionParams.stepId. Steps that are already completed or failed are not affected -- only pending steps are transitioned to skipped.

The skipStepsTo function in submit-step-result.ts walks the step list in order, skipping pending steps between the current and target:

async function skipStepsTo(tx, planId, currentStepId, targetStepId) {
  // Walk steps in order
  // After finding currentStepId, skip all pending steps
  // Stop when reaching targetStepId
}

add_steps

Signals that additional steps should be added to the plan. The current implementation resolves this action type but the AI platform is responsible for calling modify_plan with add_steps to actually insert the new steps.

fail

Immediately fails the plan. Updates research_plan.status to failed. This is one of only two ways a plan can fail (the other is a user reject decision).

continue

The default action for any unrecognized action string. Execution proceeds normally to the next pending step.

Evaluation Context

When submit_step_result evaluates branching conditions, it builds the context object from the completed step's data:

const context = {
  confidence: confidence ?? 0, // step's self-assessed confidence (0-1)
  result: result, // the structured result submitted by the AI
  status: "completed", // always "completed" at evaluation time
};

This means branching expressions can reference:

  • confidence -- the numeric confidence score
  • result.* -- any field in the step's result object
  • status -- always "completed" (branching only runs after successful completion)

Evaluation Flow in submit_step_result

After a step is marked as completed, evaluateBranchingConditions runs:

  1. Query plan_branching_condition for conditions where after_step_id matches the completed step
  2. For each condition, evaluate the expression against the context
  3. If the condition is true, resolve and execute the action
  4. If any action is fail, return { planFailed: true } immediately -- no further conditions are checked
  5. If no conditions match or all actions are continue/skip_to, proceed normally

After branching evaluation, derivePlanStatus recalculates the plan status from all step states and updates the research_plan row.

On this page