> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fibonacci.today/llms.txt
> Use this file to discover all available pages before exploring further.

# ConditionalNode

> Node for branching workflow execution based on a condition

The `ConditionalNode` class branches workflow execution based on a single condition, routing to one set of nodes when the condition is true and another when it is false.

## Constructor

```python theme={null}
from fibonacci import ConditionalNode

node = ConditionalNode(
    id="check_sentiment",
    name="Check Sentiment",
    left_value="{{analyze_sentiment}}",
    operator="contains",
    right_value="positive",
    true_branch=["celebrate"],
    false_branch=["investigate"],
    dependencies=["analyze_sentiment"]
)
```

### Parameters

| Parameter      | Type        | Default  | Description                                                                                      |
| -------------- | ----------- | -------- | ------------------------------------------------------------------------------------------------ |
| `id`           | `str`       | Required | Unique node identifier (lowercase letters, numbers, underscores, hyphens)                        |
| `name`         | `str`       | Required | Human-readable node name                                                                         |
| `left_value`   | `str`       | Required | Left side of the condition — supports template variables like `{{node_id}}` or `{{input.field}}` |
| `operator`     | `str`       | Required | Comparison operator (see table below)                                                            |
| `right_value`  | `str`       | `""`     | Right side of the condition — leave empty for `is_empty` / `is_not_empty` operators              |
| `true_branch`  | `list[str]` | `[]`     | Node IDs to execute when the condition is **true**                                               |
| `false_branch` | `list[str]` | `[]`     | Node IDs to execute when the condition is **false**                                              |
| `dependencies` | `list[str]` | `[]`     | Node IDs this node waits for before running                                                      |
| `enable_retry` | `bool`      | `False`  | Retry on failure                                                                                 |
| `max_retries`  | `int`       | `3`      | Maximum retry attempts (used when `enable_retry=True`)                                           |
| `retry_delay`  | `float`     | `1.0`    | Initial delay in seconds between retries                                                         |

## Supported Operators

The `operator` field accepts exactly these 10 values:

| Operator       | Description                                                 | Example                                |
| -------------- | ----------------------------------------------------------- | -------------------------------------- |
| `equals`       | Exact string or numeric match                               | `"completed"` equals `"completed"`     |
| `not_equals`   | Not equal                                                   | `"pending"` not\_equals `"completed"`  |
| `contains`     | Left value contains right value as a substring              | `"error on line 5"` contains `"error"` |
| `not_contains` | Left value does **not** contain right value                 | `"success"` not\_contains `"error"`    |
| `greater_than` | Numeric greater-than                                        | `100` greater\_than `50`               |
| `less_than`    | Numeric less-than                                           | `25` less\_than `50`                   |
| `starts_with`  | Left value starts with right value                          | `"error: msg"` starts\_with `"error"`  |
| `ends_with`    | Left value ends with right value                            | `"report.pdf"` ends\_with `".pdf"`     |
| `is_empty`     | Left value is empty or null (`right_value` is ignored)      | `""` is\_empty                         |
| `is_not_empty` | Left value has a non-empty value (`right_value` is ignored) | `"some text"` is\_not\_empty           |

<Warning>
  Only these 10 operators are supported. Using any other string will raise a `ValidationError` at workflow build time.
</Warning>

## Basic Examples

### Simple True/False Branch

```python theme={null}
from fibonacci import Workflow, LLMNode, ToolNode, ConditionalNode

wf = Workflow(name="feedback-router")

# Step 1: Classify the feedback
classify = LLMNode(
    id="classify",
    name="Classify Feedback",
    instruction="Classify this feedback as positive or negative. Reply with one word only.\n\n{{input.feedback}}"
)

# Step 2: Route based on result
router = ConditionalNode(
    id="router",
    name="Route Feedback",
    left_value="{{classify}}",
    operator="contains",
    right_value="negative",
    true_branch=["escalate"],
    false_branch=["log_ok"],
    dependencies=["classify"]
)

# True branch
escalate = ToolNode(
    id="escalate",
    name="Escalate to Support",
    tool="slack_send_message",
    params={
        "channel": "#support",
        "message": "Negative feedback: {{input.feedback}}"
    },
    dependencies=["router"]
)

# False branch
log_ok = ToolNode(
    id="log_ok",
    name="Log Positive Feedback",
    tool="google_sheets_append",
    params={
        "spreadsheet_id": "{{input.sheet_id}}",
        "range": "Log!A:B",
        "values": [["{{input.feedback}}", "positive"]]
    },
    dependencies=["router"]
)

wf.add_nodes([classify, router, escalate, log_ok])
```

### Numeric Threshold

```python theme={null}
check_score = ConditionalNode(
    id="check_score",
    name="Score Threshold",
    left_value="{{evaluate}}",
    operator="greater_than",
    right_value="7",
    true_branch=["publish"],
    false_branch=["revise"],
    dependencies=["evaluate"]
)
```

### String Prefix/Suffix Check

```python theme={null}
check_type = ConditionalNode(
    id="check_type",
    name="Check File Type",
    left_value="{{input.filename}}",
    operator="ends_with",
    right_value=".pdf",
    true_branch=["process_pdf"],
    false_branch=["unsupported_format"]
)
```

### Empty Value Guard

```python theme={null}
check_data = ConditionalNode(
    id="check_data",
    name="Data Present?",
    left_value="{{fetch_data}}",
    operator="is_not_empty",
    right_value="",   # ignored for is_empty / is_not_empty
    true_branch=["process_data"],
    false_branch=["send_no_data_alert"]
)
```

## Chaining Conditions (AND / OR Logic)

The `ConditionalNode` evaluates a single condition. Chain multiple nodes together to build AND / OR logic.

### AND Logic — chain nodes

```python theme={null}
# First check: sentiment negative?
check_sentiment = ConditionalNode(
    id="check_sentiment",
    name="Negative Sentiment?",
    left_value="{{analyze}}",
    operator="contains",
    right_value="negative",
    true_branch=["check_urgency"],   # only continue AND chain when true
    false_branch=["standard_response"],
    dependencies=["analyze"]
)

# Second check: urgency high? (only reached when sentiment IS negative)
check_urgency = ConditionalNode(
    id="check_urgency",
    name="High Urgency?",
    left_value="{{analyze_urgency}}",
    operator="equals",
    right_value="high",
    true_branch=["escalate"],         # negative AND high urgency
    false_branch=["soft_response"],
    dependencies=["check_sentiment"]
)
```

### OR Logic — multiple conditions pointing to the same branch

```python theme={null}
# Escalate if sentiment is negative
check_neg = ConditionalNode(
    id="check_neg",
    name="Negative?",
    left_value="{{analyze.sentiment}}",
    operator="equals",
    right_value="negative",
    true_branch=["escalate"],
    false_branch=["check_critical"],
    dependencies=["analyze"]
)

# OR escalate if category is complaint
check_critical = ConditionalNode(
    id="check_critical",
    name="Complaint?",
    left_value="{{analyze.category}}",
    operator="equals",
    right_value="complaint",
    true_branch=["escalate"],    # same escalate node
    false_branch=["normal_flow"],
    dependencies=["check_neg"]
)
```

## Per-Node Condition (Conditional Execution)

Every node type supports `.with_condition()` to skip execution unless a condition is met:

```python theme={null}
# Only send email if the user opted in
send_email = ToolNode(
    id="send_email",
    name="Send Email",
    tool="gmail_send",
    params={...}
).with_condition(
    left_value="{{input.email_opt_in}}",
    operator="equals",
    right_value="true"
)

# Only run expensive analysis on large payloads
deep_analysis = LLMNode(
    id="deep_analysis",
    name="Deep Analysis",
    instruction="..."
).with_condition(
    left_value="{{input.data_size}}",
    operator="greater_than",
    right_value="1000"
)
```

## Retry Configuration

Use `.with_retry()` to enable retries on the conditional node itself:

```python theme={null}
router = ConditionalNode(
    id="router",
    name="Route Request",
    left_value="{{classify}}",
    operator="equals",
    right_value="urgent",
    true_branch=["urgent_handler"],
    false_branch=["standard_handler"],
    dependencies=["classify"]
).with_retry(max_retries=3, delay=1.0)
```

## YAML Configuration

```yaml theme={null}
nodes:
  - id: router
    name: Route by Sentiment
    type: condition
    left_value: "{{analyze}}"
    operator: contains
    right_value: "negative"
    true_branch:
      - escalate
    false_branch:
      - log_ok
    dependencies:
      - analyze
```

## Complete Example

```python theme={null}
from fibonacci import Workflow, LLMNode, ToolNode, ConditionalNode

wf = Workflow(name="support-ticket-router")

# Classify ticket
classify = LLMNode(
    id="classify",
    name="Classify Ticket",
    instruction="""Classify this support ticket.
Respond with exactly one word: urgent, billing, technical, or general.

Ticket: {{input.message}}"""
)

# Route urgent tickets immediately
check_urgent = ConditionalNode(
    id="check_urgent",
    name="Urgent?",
    left_value="{{classify}}",
    operator="equals",
    right_value="urgent",
    true_branch=["page_on_call"],
    false_branch=["check_billing"],
    dependencies=["classify"]
)

# Route billing tickets
check_billing = ConditionalNode(
    id="check_billing",
    name="Billing Issue?",
    left_value="{{classify}}",
    operator="equals",
    right_value="billing",
    true_branch=["billing_team"],
    false_branch=["tech_team"],
    dependencies=["check_urgent"]
)

# Urgent: page on-call engineer
page_on_call = ToolNode(
    id="page_on_call",
    name="Page On-Call",
    tool="slack_send_message",
    params={
        "channel": "#on-call",
        "message": "🚨 Urgent ticket: {{input.message}}"
    },
    dependencies=["check_urgent"]
)

# Billing team
billing_team = ToolNode(
    id="billing_team",
    name="Notify Billing",
    tool="slack_send_message",
    params={
        "channel": "#billing",
        "message": "Billing ticket: {{input.message}}"
    },
    dependencies=["check_billing"]
)

# Tech support (default)
tech_team = LLMNode(
    id="tech_team",
    name="Tech Response",
    instruction="Provide a helpful technical response to: {{input.message}}",
    dependencies=["check_billing"]
)

wf.add_nodes([classify, check_urgent, check_billing, page_on_call, billing_team, tech_team])

result = wf.run(input_data={"message": "My payment failed!"})
print(result.output_data)
```
