diff --git a/docs/user-guide/workflows/configuration/configuration-reference.md b/docs/user-guide/workflows/configuration/configuration-reference.md index ed80c84c..39d1b7d2 100644 --- a/docs/user-guide/workflows/configuration/configuration-reference.md +++ b/docs/user-guide/workflows/configuration/configuration-reference.md @@ -58,7 +58,7 @@ assistants: [Troubleshooting](./troubleshooting.md#tool-output-token-limit-exceeded) for diagnosis and mitigation strategies. - **exclude_extra_context_tools**: Disable automatic context tools -- **skills**: List of skill IDs to attach to the assistant. Skills load on demand during execution based on relevance. See [Skills in Workflow Assistants](../../skills/skills-in-workflow.md) for usage details. +- **skill_ids**: List of skill IDs to attach to the assistant. Skills load on demand during execution based on relevance. See [Skills in Workflow Assistants](../../skills/skills-in-workflow.md) for usage details. - **mcp_servers**: List of MCP server configurations (see Section 3.6) #### Tool Configuration: @@ -552,15 +552,48 @@ custom_nodes: #### Custom Node Types: -- **state_processor_node**: Process and aggregate state outputs -- **bedrock_flow_node**: AWS Bedrock Flows integration -- **generate_documents_tree**: Generate document tree structure -- Additional custom implementations +`custom_node_id` selects the node implementation. The accepted values are: + +| `custom_node_id` | Purpose | Details | +| ------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------- | +| `transform_node` | Map and reshape data without an LLM call | [Transform Node](./specialized-nodes.md#84-transform-node) | +| `state_processor_node` | Process and aggregate outputs from multiple states | [State Processor Node](./specialized-nodes.md#81-state-processor-node) | +| `generate_documents_tree` | Generate a document tree from data sources | [Document Tree Generator](./specialized-nodes.md#83-document-tree-generator) | +| `bedrock_flow_node` | Run an AWS Bedrock Flow | [Bedrock Flow Node](./specialized-nodes.md#82-bedrock-flow-node) | + +:::warning `custom_node_id` means two different things +The name appears at both levels of a custom node and refers to a different thing in each: + +- In the **`custom_nodes` list**, `custom_node_id` is the **node type** — one of the values above. +- In a **state**, `custom_node_id` is a **reference to `custom_nodes[].id`** — the declared + instance, not the type. + +```yaml +custom_nodes: + - id: extract_pr_fields # ← what states reference + custom_node_id: transform_node # ← the implementation + +states: + - id: parse-webhook + custom_node_id: extract_pr_fields # ✅ matches custom_nodes[].id + next: + state_id: route + + - id: parse-webhook-broken + custom_node_id: transform_node # ❌ this is a type, not a declared instance + next: + state_id: route +``` + +Referencing a type from a state fails cross-reference validation when the workflow is saved. +Misspelling the **type** is not caught at save time — it fails at execution with +` class not found in codemie.workflows.nodes package`. +::: #### Common Properties: -- **id**: Unique identifier -- **custom_node_id**: Node type identifier +- **id**: Unique identifier, referenced by states +- **custom_node_id**: Node type — one of the values in the table above - **name**: Display name - **model**: LLM model for processing - **system_prompt**: Custom instructions diff --git a/docs/user-guide/workflows/configuration/specialized-nodes.md b/docs/user-guide/workflows/configuration/specialized-nodes.md index 704278f9..56fac300 100644 --- a/docs/user-guide/workflows/configuration/specialized-nodes.md +++ b/docs/user-guide/workflows/configuration/specialized-nodes.md @@ -143,6 +143,7 @@ custom_nodes: | `mappings` | array | Yes | - | List of transformation mappings | | `output_schema` | object | No | - | JSON Schema for output validation | | `on_error` | string | No | `fail` | Error strategy: `fail`, `skip`, `default`, `partial` | +| `default_output` | object | No | `{}` | Output returned when `on_error` is `default` | | `default_output` | object | No | `{}` | Default output when `on_error: default` | #### Input Key Path Extraction diff --git a/docs/user-guide/workflows/configuration/state-transitions.md b/docs/user-guide/workflows/configuration/state-transitions.md index c34b18b2..4c005cbb 100644 --- a/docs/user-guide/workflows/configuration/state-transitions.md +++ b/docs/user-guide/workflows/configuration/state-transitions.md @@ -7,6 +7,8 @@ pagination_next: user-guide/workflows/configuration/context-management sidebar_position: 5 --- + + # State Transitions ## 5. State Transitions @@ -48,17 +50,77 @@ Conditional transitions allow branching based on the execution result from the p 1. The previous state's output is parsed (JSON parsing is attempted automatically) 2. If the output is a dictionary, all keys become variables accessible in the expression -3. The expression is evaluated using Python's `eval()` with the parsed variables +3. The expression is evaluated by a restricted expression evaluator using those variables 4. Based on the boolean result, workflow transitions to `then` or `otherwise` state +:::warning Expressions see only the deciding state's own output + +The variables available to a condition come **exclusively from the output of the state that +declares the condition**. The context store is not in scope, so a value written by an earlier +state is not visible here unless the deciding state re-emits it in its own output. + +```yaml +states: + - id: fetch-user + assistant_id: fetcher + next: + state_id: check-tier + output_key: user_record # written to the context store + + - id: check-tier + assistant_id: classifier + next: + condition: + # ❌ tier came from fetch-user, not from check-tier — undefined here + expression: "tier == 'premium'" + then: premium-path + otherwise: standard-path +``` + +To branch on an earlier value, have the deciding state emit it — a [Transform Node](./specialized-nodes.md#84-transform-node) +is the cheapest way to lift context-store keys into a state output without an LLM call. + +::: + #### Conditional Expression Syntax: -- **Comparison operators**: `>`, `<`, `>=`, `<=`, `==`, `!=` +- **Comparison operators**: `>`, `<`, `>=`, `<=`, `==`, `!=`, `is`, `is not` - **Logical operators**: `and`, `or`, `not` -- **String methods**: `in` operator, `.startswith()`, `.endswith()`, `.contains()` +- **Membership and indexing**: `in`, subscript access (`payload["status"]`, `items[0]`) +- **String methods**: any public string method, such as `.lower()`, `.startswith()`, `.endswith()` +- **Built-in functions**: `len`, `min`, `max`, `sum`, `abs`, `round`, `sorted`, `any`, `all`, `str`, `int`, `float`, `bool`, `list`, `dict`, `set`, `tuple`, `isinstance`, `enumerate`, `zip`, `map`, `filter`, `reversed` - **Variable references**: Use variable names directly (no `{{}}` needed in expressions) - **Special variable**: `keys` - automatically available, contains all keys from the result dictionary +**Not supported**: list/dict comprehensions, lambdas, assignments, imports, and any attribute +beginning with an underscore. These are rejected by the evaluator rather than executed. + +:::warning Dotted access into a nested value does not work + +`payload.status` is rejected by the evaluator as an unsafe construct, which — like every other +evaluation failure — yields `False` and routes to `otherwise`. Only the top-level names from the +state's own output are variables; reach inside them with a subscript instead: + +```yaml +expression: 'payload["status"] == "ok"' # ✅ +expression: "payload.status == 'ok'" # ❌ blocked, silently takes otherwise +``` + +The execution log records `Condition expression blocked - unsafe construct` when this happens. + +::: + +#### Boolean Literals Must Be Python-Style + +Expressions are Python, not YAML. Boolean literals are `True` and `False`, capitalized. +Lowercase `true`/`false` are normalized to Python booleans when the workflow is saved, but write +them capitalized so the expression reads the same everywhere it appears: + +```yaml +expression: "is_approved == True" # ✅ +expression: "is_approved == true" # ⚠️ normalized on save — prefer True +``` + #### Examples: ```yaml @@ -97,9 +159,29 @@ condition: - Variables are referenced by name only (e.g., `status`, not `{{status}}`) - The expression must evaluate to a boolean value -- If expression evaluation fails, workflow transitions to `otherwise` state - String values are automatically converted; `'true'`/`'false'` strings become booleans +#### Every Failure Routes to `otherwise` + +A condition that cannot be evaluated does not fail the workflow — it evaluates to `False` and the +workflow takes the `otherwise` branch. This applies to all of the following: + +| What happened | Example | +| ---------------------------------- | ------------------------------------- | +| Variable not in the state's output | `tier == 'premium'` (see note above) | +| Misspelled variable name | `statuss == 'success'` | +| Unsupported construct | `[x for x in items if x.ok]` | +| Method or attribute does not exist | `'error' in message.contains('x')` | +| Type mismatch during comparison | `count > 10` where `count` is `"ten"` | + +Because the workflow still completes, a misspelling looks like a business-logic outcome rather +than a bug. When a branch always goes the same way, check the execution logs for +`Condition expression blocked`, `Condition expression has invalid syntax`, or +`Error evaluating condition` before assuming the data is at fault. + +The same rule applies to switch cases: a case that fails to evaluate is treated as not matching, +and evaluation continues with the next case, falling through to `default`. + ### 5.4 Switch/Case Transitions ```yaml @@ -376,6 +458,81 @@ next: append_to_context: true # Each iteration appends its output; context_store["processed_items"] becomes a list ``` +**finish_iteration** (boolean, default: `false`) — _state-level, not inside `next`_: + +- Marks a state as the **last step of the per-item chain** +- While `finish_iteration` is `false`, each state in the chain forwards the same item onward, keeping the branch alive +- Setting it to `true` ends the per-item branch, allowing the workflow to converge (fan-in) +- Set it on **every terminal state of the chain**. When the chain branches with a condition, each branch needs its own `finish_iteration: true` — the state that evaluates the condition does not get it +- Pair it with `append_to_context: true` so each branch's result is collected rather than overwritten + +`iter_key` belongs on the state that **produces** the collection, not on the branching states. +The schema rejects `iter_key` in the same `next` block as a `condition` or `switch`. + +```yaml +states: + - id: list-candidates + assistant_id: scorer + task: List the candidates. + next: + state_id: score-item + iter_key: candidates # the producer starts the fan-out + + - id: score-item + assistant_id: scorer + task: Score this candidate. + next: + condition: # the evaluator only routes + expression: "score >= 7" + then: keep-item + otherwise: drop-item + + - id: keep-item + assistant_id: writer + finish_iteration: true # terminal branch + next: + state_id: summarize + output_key: kept + append_to_context: true + + - id: drop-item + assistant_id: writer + finish_iteration: true # the other terminal branch + next: + state_id: summarize + output_key: dropped + append_to_context: true + + - id: summarize + assistant_id: writer + next: + state_id: end +``` + +**include_in_iterator_context** (array of strings, default: `["*"]`): + +- Whitelist of context store keys copied into **each** parallel branch +- The default `["*"]` copies the entire context store into every branch — with N items, the store is duplicated N times inside the execution checkpoint +- Large values (fetched file contents, API responses, document batches) multiplied across many branches can push the checkpoint past the database row size limit and fail the execution +- Naming only the keys the per-item states actually read keeps branches small. The parent context store is untouched, so keys left out are still available after the fan-in + +```yaml +next: + state_id: review-item + iter_key: review_batches + include_in_iterator_context: ['current_goal', 'channel', 'jira_project_key'] + # review_batches itself stays in the parent store — branches get only the three small keys +``` + +**override_task** (boolean, default: `false`): + +- Controls what the **next state in the per-item chain** receives as its item +- `false` (default): the next state receives the original item, unchanged — every state in the chain sees the same input +- `true`: the next state receives **this state's output** instead, so the item is progressively rewritten as it moves down the chain + +Use `true` for refinement pipelines (draft → edit → polish, where each step consumes the previous +step's version) and leave it `false` when several states must each inspect the same original item. + #### Multi-Stage Iteration: For multi-stage iteration (when you have multiple sequential states processing each item), the **same `iter_key` must be present in every state** included in the iteration chain. diff --git a/docs/user-guide/workflows/configuration/workflow-states.md b/docs/user-guide/workflows/configuration/workflow-states.md index 48be687d..afb3c0a6 100644 --- a/docs/user-guide/workflows/configuration/workflow-states.md +++ b/docs/user-guide/workflows/configuration/workflow-states.md @@ -37,14 +37,56 @@ states: - **task**: Instructions for the assistant - **next**: State transition configuration - **resolve_dynamic_values_in_prompt**: Enable template variable resolution -- **output_schema**: JSON schema for structured output (optional) +- **output_schema**: Structured output definition (optional) — see [Two Modes of `output_schema`](#two-modes-of-output_schema) - **interrupt_before**: Pause workflow for user confirmation - **retry_policy**: Custom retry configuration +- **finish_iteration**: Marks the last step of a per-item chain — see [Iteration Properties](./state-transitions.md#iteration-properties) +- **result_as_human_message**: Add this state's output to the message history as a user message instead of an assistant message (default `false`) :::note `interrupt_before` was previously named `wait_for_user_confirmation`. If you encounter this legacy term in older workflow configurations or documentation, it refers to the same feature. ::: +#### Two Modes of `output_schema` + +`output_schema` accepts a YAML block scalar containing JSON, and its contents decide how strongly +the output is enforced. Both forms are valid; they give different guarantees. + +**Mode 1 — a real JSON Schema: enforced.** When the value parses as a valid JSON Schema, the +assistant is switched into structured-output mode and the model provider constrains the response +to match. Fields and types are guaranteed. + +```yaml +output_schema: | + { + "type": "object", + "properties": { + "severity": { "type": "string", "enum": ["low", "medium", "high"] }, + "findings": { "type": "array", "items": { "type": "string" } } + }, + "required": ["severity"] + } +``` + +**Mode 2 — a shape example: a suggestion only.** When the value is JSON but not a valid schema — +typically an object whose values describe the fields in prose — it is appended to the system +prompt as guidance. The model usually follows it, but nothing enforces it, and a downstream +condition reading a missing field will silently take the `otherwise` branch. + +```yaml +output_schema: | + { + "severity": "One of: low, medium, high", + "findings": "List of issue descriptions" + } +``` + +:::tip +Use Mode 2 for readability while drafting, and switch to Mode 1 before any state's output is +consumed by a condition, a `switch`, or an `iter_key`. Those three read fields positionally and +have no way to recover when a field is absent. +::: + #### Agent State Advanced Examples: **Example 1: Using `output_schema` for Structured Output** @@ -76,9 +118,8 @@ states: }, "required": ["name", "email"] } - # The LLM output is validated against this schema - # If validation fails, the LLM is re-prompted automatically - # Output is guaranteed to match the schema structure + # A valid JSON Schema puts the assistant into structured-output mode, + # so the model provider constrains the response to match it next: state_id: process-user-data diff --git a/docs/user-guide/workflows/create-workflow.md b/docs/user-guide/workflows/create-workflow.md index 095d9a8a..602b1498 100644 --- a/docs/user-guide/workflows/create-workflow.md +++ b/docs/user-guide/workflows/create-workflow.md @@ -299,6 +299,28 @@ Switch to YAML editing when you need features like Jinja templating, complex con For detailed YAML configuration syntax and features, see the [Workflow YAML Configuration Guide](./configuration/introduction.md). ::: +### Editor layout keys in the YAML + +Alongside the execution configuration, the Visual Editor stores the canvas layout in the same +YAML document: + +- **`meta_states`** — a top-level list holding each node's position, size, and editor node type, + plus the decision nodes (`switch`, `conditional`) that the editor draws as separate boxes +- **`next.meta_next_state_id`** — links a transition to the decision node that represents it on + the canvas + +These keys are read only by the editor. The execution engine ignores them, and they have no +effect on how a workflow runs. + +:::warning Editing YAML outside the editor +When a workflow is modified through the API, the SDK, the CLI, or an IaC pipeline, the existing +`meta_states` block must be carried through unchanged. Dropping it discards the arrangement +someone laid out by hand. + +Writing a workflow from scratch without `meta_states` is safe — the editor lays the graph out +automatically the first time it opens, then saves the resulting positions. +::: + ## Saving and Running Workflows ### Save Options diff --git a/faq/how-do-i-automatically-generate-yaml-for-workflows.md b/faq/how-do-i-automatically-generate-yaml-for-workflows.md index 04e2dac9..61ad3510 100644 --- a/faq/how-do-i-automatically-generate-yaml-for-workflows.md +++ b/faq/how-do-i-automatically-generate-yaml-for-workflows.md @@ -1,121 +1,80 @@ -# How do I automatically generate YAML for workflows? What is the AutoYam Assistant in CodeMie? How to convert workflow requirements into YAML configurations? - -AutoYaml Assistant: Generate Workflow YAML Configurations Automatically - -The AutoYaml Assistant is a feature that helps you quickly generate YAML configurations for workflows in CodeMie. This tool significantly speeds up the workflow creation process by providing intelligent suggestions and pre-configured templates based on your requirements. - -## How to Use the AutoYaml Assistant - -## Generating a New Workflow Configuration - -1. **Access the AutoYaml Assistant** - - Navigate to the workflows section in CodeMie - - Click on "Create New Workflow" - - Select "Use AutoYaml Assistant" option - -2. **Define Your Workflow Requirements** - - The assistant will prompt you for necessary workflow information: - - Number and types of assistants needed - - States and transitions between them - - Specific tasks for each state - - Conditions for state transitions (if required) - - Provide detailed responses to get the most accurate YAML configuration - -3. **Review and Edit the Generated YAML** - - The assistant will generate a complete YAML configuration based on your requirements - - Review the generated configuration in the editor - - Make adjustments as needed using the built-in YAML editor - - The system will validate your configuration for syntax and logical errors - -4. **Save and Apply Your Configuration** - - Once satisfied with the configuration, click "Save" to apply it - - The system will perform final validation to ensure all referenced components exist - - Your workflow is now ready to use - -## Editing an Existing Workflow Configuration - -1. **Select an Existing Workflow** - - Navigate to the workflows section - - Select the workflow you want to modify - -2. **Use AutoYaml Assistant for Editing** - - Click "Edit with AutoYaml Assistant" - - Paste or upload your current YAML configuration - - Describe the changes you want to make - -3. **Review and Apply Changes** - - The assistant will update your configuration based on your requirements - - Review the changes and make additional edits if needed - - Save the updated configuration to apply changes to your workflow - -## Examples and Use Cases - -Example 1: Creating a Simple Two-State Workflow - -``` - User input: "I need a workflow with two states. The first state searches for information, the second state summarizes it." - - Generated YAML: -assistants: - - id: researcher - assistant_id: [your_assistant_id] - model: 'gpt-4o' - - id: summarizer - assistant_id: [your_assistant_id] - model: 'gpt-4o' - -states: - - id: research_state - assistant_id: researcher - task: | - Search for relevant information on the provided topic. - output_schema: | - { - "research_results": "Research findings and information" - } - next: - state_id: summary_state - - id: summary_state - assistant_id: summarizer - task: | - Summarize the research findings into a concise report. - output_schema: | - { - "summary": "Concise summary of the research findings" - } - next: - state_id: end -``` - -Example 2: Adding a Conditional Branch - -``` - User input: "Add a condition to check if research found sufficient information, if not, repeat research." - - Assistant adds: - output_schema: | - { - "research_results": "Research findings and information", - "sufficient_info": "Boolean indicating if information is sufficient" - } - next: - condition: - expression: "sufficient_info == True" - then: summary_state - otherwise: research_state -``` - -## Tips for Effective Use - -1. **Be Specific in Your Requirements**: The more detailed your requirements, the more accurate the generated configuration. - -2. **Start Simple, Then Expand**: Begin with a simple workflow and use the assistant to add complexity as needed. - -3. **Review the Documentation**: Refer to the [Workflow YAML Specification](link-to-existing-docs) to understand all available options. - -4. **Validate Your Configuration**: Always use the validation feature to ensure your workflow meets all requirements. +# How do I automatically generate YAML for workflows? How do I generate a workflow with AI in CodeMie? How to convert workflow requirements into YAML configurations? + +CodeMie can draft a workflow configuration from a plain-language description, and can apply +plain-language changes to a configuration you already have. Two features cover this: + +- **Generate Workflow with AI** — creates a new workflow from a description +- **Refine Workflow with AI** — rewrites an existing workflow's YAML from an instruction + +Both are available in the workflow editor. If you do not see them, AI workflow generation is not +enabled on your CodeMie instance — ask your administrator. + +## Generate a New Workflow + +1. Go to **Workflows** and click **+ Create Workflow** +2. Open the **Generate Workflow with AI** dialog +3. Describe what the workflow should do, for example: + + > I need a workflow that processes incoming support tickets, categorizes them by priority, and + > routes them to the appropriate team + +4. Click **Generate with AI** +5. The generated configuration opens in the editor — review it in either the visual view or the + YAML view before saving + +The generated configuration is not saved automatically. Nothing changes on the platform until you +click **Save**. + +## Refine an Existing Workflow + +1. Open the workflow and enter edit mode +2. Open the **Refine Workflow with AI** dialog +3. Describe the change you want, for example: + + > Add retry logic to the LLM step and improve error handling throughout the workflow + +4. Click **Refine with AI** +5. The rewritten YAML replaces the editor's current contents — review it, then save or discard + +Refinement rewrites the whole configuration rather than patching it, so review the full diff +rather than just the part you asked about. + +## What AI Generation Produces — and What It Does Not + +Both features build workflows out of **assistant states**: each step is an LLM call against an +assistant, wired together with sequential, conditional, switch, parallel, or iterative +transitions. + +They do **not** produce: + +- **Tool states** — direct tool calls that run without an LLM +- **Custom node states** — including Transform Nodes, the standard way to reshape webhook payloads + and API responses without an LLM call +- **Sub-workflow states** — calls into another workflow + +Workflows built mostly from tool calls and Transform Nodes — webhook handlers, compliance checks, +API orchestration — have to be written directly, either in the YAML view or by assembling nodes in +the [Visual Editor](https://docs.codemie.ai/user-guide/workflows/create-workflow). A good approach +is to generate the assistant-driven part first, then add the tool and transform steps by hand. + +## Tips for Better Results + +1. **Be specific about inputs and outputs.** Say what the workflow receives and what each step + should produce, not just what it should do. +2. **Name the branches.** Describe the decision points and what happens on each side, so the + generator produces conditions rather than a straight line. +3. **Start small, then refine.** Generate a three or four step version, check it runs, then use + **Refine with AI** to add error handling and edge cases. +4. **Check conditional expressions.** These are Python expressions evaluated against the deciding + state's own output. Verify that every variable a condition references is actually a field of + that state's output — see + [Conditional Transitions](https://docs.codemie.ai/user-guide/workflows/configuration/state-transitions). +5. **Set `output_schema` on any state a condition reads.** A real JSON Schema makes the fields + guaranteed; a plain example shape does not. ## Sources - [Create Workflow](https://docs.codemie.ai/user-guide/workflows/create-workflow) +- [Configuration Reference](https://docs.codemie.ai/user-guide/workflows/configuration/configuration-reference) +- [State Transitions](https://docs.codemie.ai/user-guide/workflows/configuration/state-transitions) - [Examples](https://docs.codemie.ai/user-guide/workflows/configuration/examples) diff --git a/faq/why-does-my-workflow-always-take-the-otherwise-branch.md b/faq/why-does-my-workflow-always-take-the-otherwise-branch.md new file mode 100644 index 00000000..e51bdec0 --- /dev/null +++ b/faq/why-does-my-workflow-always-take-the-otherwise-branch.md @@ -0,0 +1,101 @@ +# Why does my workflow condition always take the "otherwise" branch? Why does my switch always fall through to "default"? + +Almost always because the variable in the expression is not visible to it. + +A condition or switch expression is evaluated against the **parsed output of the state that +declares it** — and nothing else. The context store is not in scope, so a value written by an +earlier state is invisible here even though `{{placeholders}}` elsewhere can read it. + +When a variable is undefined, the expression does not raise an error that stops the workflow. +It evaluates to `false`, the `otherwise` branch is taken (or the switch falls through to +`default`), and the execution finishes successfully. The routing is wrong but nothing looks +broken. + +## What breaks it + +```yaml +states: + - id: fetch-user + assistant_id: fetcher + next: + state_id: check-tier + output_key: user_record # goes to the context store + + - id: check-tier + assistant_id: classifier + next: + condition: + # tier belongs to fetch-user's output, not to check-tier — undefined here + expression: "tier == 'premium'" + then: premium-path + otherwise: standard-path +``` + +## How to fix it + +**Make the deciding state emit the value.** Add the field to that state's own output, then +reference it by its bare name: + +```yaml +- id: check-tier + assistant_id: classifier + output_schema: | + { + "type": "object", + "properties": { "tier": { "type": "string" } }, + "required": ["tier"] + } + next: + condition: + expression: "tier == 'premium'" + then: premium-path + otherwise: standard-path +``` + +**Or lift the context value with a Transform Node.** This costs no LLM call and is the usual +approach when the value is already in the context store: + +```yaml +custom_nodes: + - id: lift_tier + custom_node_id: transform_node + config: + input_source: 'context_store' + mappings: + - output_field: 'tier' + type: 'extract' + source_path: 'user_record.tier' + +states: + - id: route-by-tier + custom_node_id: lift_tier + next: + condition: + expression: "tier == 'premium'" + then: premium-path + otherwise: standard-path +``` + +## Other causes of the same symptom + +Every one of these evaluates to `false` rather than failing: + +- A misspelled variable name +- Dotted access into a nested value, such as `payload.status` — the evaluator rejects it as an + unsafe construct; use `payload["status"]` instead +- Lowercase `true` / `false` instead of Python's `True` / `False` +- A method that does not exist, such as `.contains()` on a string +- A type mismatch, such as `count > 10` when `count` is the string `"ten"` +- List comprehensions and lambdas, which the expression evaluator rejects +- A state whose `output_schema` is a plain example rather than a real JSON Schema, so the field + was never guaranteed to be present + +Check the execution logs for `Condition expression blocked`, +`Condition expression has invalid syntax`, or `Error evaluating condition` to confirm which one +applies. + +## Sources + +- [State Transitions — Conditional Transitions](https://docs.codemie.ai/user-guide/workflows/configuration/state-transitions) +- [Workflow States — Two Modes of output_schema](https://docs.codemie.ai/user-guide/workflows/configuration/workflow-states) +- [Specialized Nodes — Transform Node](https://docs.codemie.ai/user-guide/workflows/configuration/specialized-nodes)