Skip to content
5 changes: 3 additions & 2 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,12 @@ hide:
* **BUG** Fixed an error raised by the code in a [Python node](tech-hub/python_node.md) causing the message to fail without a reply. The participant now receives a generic error response, and the error is recorded against the message so you can see what went wrong.

## Aug 20, 2026
* **CHANGE** Turning off a channel's **Enabled** toggle now stops new conversations and bot-initiated messages, where before it only stopped incoming ones. Previously a disabled channel still allowed new conversations to be started (from the chat widget, the public web chat link, Slack, or the chatbot management pages) and still sent out scheduled messages, event action messages and API-triggered messages. New sessions on a disabled channel are now refused, and bot-initiated messages to a disabled channel are no longer sent. Two API endpoints are still exceptions — see [Known limitations](how-to/disable_a_channel.md#known-limitations). See [Disabling a channel](concepts/channels.md#disabling-a-channel).
* **CHANGE** Turning off a channel's **Enabled** toggle now stops new conversations and bot-initiated messages, where before it only stopped incoming ones. Previously a disabled channel still allowed new conversations to be started (from the chat widget, the public web chat link, Slack, or the chatbot management pages) and still sent out scheduled messages, event action messages and API-triggered messages.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
New sessions on a disabled channel are now refused, and bot-initiated messages to a disabled channel are no longer sent. Two API endpoints are still exceptions — see [Known limitations](how-to/disable_a_channel.md#known-limitations). See [Disabling a channel](concepts/channels.md#disabling-a-channel).

## Aug 18, 2026
* **CHANGE** When several branches merge into one node, that node now takes its input from the branch that arrived most recently, and `node_inputs` holds every input that has arrived so far instead of just one. Previously the input depended on the order the connections happened to be drawn, so the same graph could feed a merge node a different branch — any node with more than one incoming connection may now receive a different input than before. See [Which input a node receives](concepts/pipelines/parallel.md#which-input-a-node-receives).
* **BUG** Fixed merge nodes that wait until they have a set number of inputs (for example a Python node checking `len(node_inputs)`) never completing, which made the pipeline return nothing. See [Optional Parallel Branches](concepts/pipelines/parallel.md#optional-parallel-branches).
* **BUG** Fixed merge nodes that wait until they have a set number of inputs (for example a Python node checking `len(node_inputs)`) never completing, which made the pipeline return nothing. See [Merging branches that are optional](tech-hub/merging_parallel_branches.md#merging-branches-that-are-optional).
* **BUG** Fixed Python node code failing with `NameError: name 'enumerate' is not defined`. The `enumerate` builtin can now be used in your scripts. See [Python Node](tech-hub/python_node.md).

## Aug 17, 2026
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/pipelines/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Common node types include:

- **[LLM Node](nodes.md#llm-node)** — Processes messages using an AI model. Handles natural conversations, answering questions, and generating responses.

- **[Routing Nodes](nodes.md#routing-nodes)** — Makes decisions about which path the conversation should take based on the message content. Useful for directing different types of questions to different handling logic, or routing based on participant intent.
- **[Routing Nodes](nodes.md#routing-nodes)** — Makes decisions about which path the conversation should take, based on message content or on data already known about the participant. Useful for directing different types of questions to different handling logic, or routing on participant attributes like subscription tier.

- **[Python Node](nodes.md#python-node)** — Runs custom code to handle complex logic, fetch data from external systems, process attachments, or manipulate participant data.

Expand Down
4 changes: 2 additions & 2 deletions docs/concepts/pipelines/nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ See the [Render a Template and Send an Email Node](../../tech-hub/template_and_e

## Extract Structured Data Node

Extract structured data from the input. This node acts as a passthrough, meaning the output will be identical to the input, allowing it to be used in a pipeline without affecting the conversation.
Uses an LLM to extract structured data from the input against a JSON schema you define. Unlike most other nodes, its output **replaces** the input: downstream nodes receive the extracted data (as JSON) rather than the original conversation text.

## Update Participant Data Node

Extract structured data and save it as participant data. This node is commonly used with [events](../events.md).
Uses an LLM to extract structured data the same way as the Extract Structured Data node. Instead of passing the result downstream, it saves it as [participant data](../../concepts/participant_data.md). This node is a passthrough — its output is identical to its input. It can be inserted into a pipeline without changing what the next node receives, and is commonly used with [events](../events.md).

## Python Node

Expand Down
102 changes: 14 additions & 88 deletions docs/concepts/pipelines/parallel.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Parallel Pipelines
Nodes in a pipeline can run in parallel, allowing multiple operations to proceed simultaneously.

Nodes in a pipeline can run in parallel, allowing multiple operations to proceed simultaneously. This follows directly from [how a pipeline runs](index.md#how-a-pipeline-runs): every node whose dependencies are satisfied executes in the same pass.

```mermaid
flowchart LR
Expand All @@ -9,15 +10,17 @@ flowchart LR
```

!!! warning "Limitations"

**Cycles**

Configurations that result in cycles (recursive loops) are not supported.

**Multiple Exectuion**
**Multiple execution**

In cases where the branches of a workflow do not have the same number of nodes and then merge, nodes after the merge will be executed more than once without special handling. See the section below on [Uneven Banches](#uneven-branches)
When the branches of a workflow have different lengths and then merge, the node after the merge runs more than once unless you handle this deliberately. See [Uneven branches](#uneven-branches) below.

## Dangling nodes

Nodes without connected outputs (dangling nodes) are supported and will execute in turn. The outputs of these nodes will still be recorded in the pipeline state.

```mermaid
Expand All @@ -30,7 +33,8 @@ flowchart LR
See this pattern used in the Workflow Cookbook: [Safety check in parallel](../../how-to/workflow_cookbook.md#safety-check-in-parallel), where an unconnected **safe** output lets compliant messages pass through unchanged.

## Multiple outputs
Connecting multiple outputs from one node (e.g. a router node) to the output of another node is allowed. If the node produces more than one output over the course of the run, the most recent one is passed on as input — see [Which input a node receives](#which-input-a-node-receives).

Connecting multiple outputs from one node (e.g. a router node) to the input of another node is allowed. If the node produces more than one output over the course of the run, the most recent one is passed on as input — see [Which input a node receives](#which-input-a-node-receives).

```mermaid
flowchart LR
Expand All @@ -51,7 +55,7 @@ flowchart LR
LLM --> out([Output])
```

See this pattern used in the Workflow Cookbook: [Router for classification](../../how-to/workflow_cookbook.md#router-for-classification), where multiple category outputs feed into the same Python node.
See this pattern used in the Workflow Cookbook: [Router for classification](../../how-to/workflow_cookbook.md#router-for-classification), where multiple category outputs feed into the same [Python node](nodes.md#python-node).

## Which input a node receives

Expand Down Expand Up @@ -88,89 +92,11 @@ The execution steps are as follows:

Notice how `NodeD` gets executed twice. The first time it runs, only `NodeB` has reached it, so `NodeB`'s output is its `input` and its single `node_inputs` entry. By the second run `NodeC` has finished, so `NodeC`'s output becomes the `input` and `node_inputs` holds both.

To understand why this happens you need to understand the [execution model](index.md#how-a-pipeline-runs).

You can manage this challenge by using a `PythonNode` with some utility functions:

* `require_node_outputs`: This function will abort any node run if all the requested data is not available.
* `wait_for_next_input`: This is a lower level function that can be used when `require_node_outputs` isn't suitable.

In the example above, we could use the following code in `NodeD` to merge the outputs:

```python
def main(input, **kwargs):
# this will abort the first run since only `NodeB` has outputs
require_node_outputs("NodeB", "NodeC")
b = get_node_output("NodeB")
c = get_node_output("NodeC")
return f"{b}\n{c}"
```

Using the lower level `wait_for_next_input` function we can do the same thing:

```python
def main(input, **kwargs):
b = get_node_output("NodeB")
c = get_node_output("NodeC")
if b is None and c is None:
# abort until both are available
wait_for_next_input()
return f"{b}\n{c}"
```

## Optional Parallel Branches

This shows a use case for the `wait_for_next_input` function. We have a pipeline which has parallel branches and a merge node but not all the branches will execute.

```mermaid
flowchart LR
start([Input]) --> Router
start --> NodeA
Router -.-> NodeB
Router -.-> NodeC
NodeA --> Merge
NodeB --> Merge
NodeC --> Merge
Merge --> out([Output])
```

The `Merge` node will get outputs from `NodeA` and either `NodeB` or `NodeC`. We can't use `require_node_outputs` because not all outputs will be generated. Instead we need to use the `wait_for_next_input` function:

=== "Option 1"

```python
def main(input, **kwargs):
b = get_node_output("NodeB")
c = get_node_output("NodeC")
b_or_c = b or c
if not b_or_c:
# wait until we have either b or c
wait_for_next_input()
a = get_node_output("NodeA")
return f"{a}\n{b_or_c}"
```

Note that we don't need to check if we have output from `NodeA` since it will be guaranteed to be available by the time `NodeB` or `NodeC` execute due to the execution order.

=== "Option 2"

This option makes use of the [`node_inputs`](../../tech-hub/python_node.md#additional-keyword-arguments) keyword argument which contains a list of all the inputs available to the current node execution. Since we want to wait until we have inputs from `NodeA and (NodeB or NodeC)` we can check that the inputs list has at least two values.

```python
def main(input, **kwargs):
all_inputs = kwargs.get("node_inputs", [])
if len(all_inputs) < 2:
# wait until we have at least two inputs
wait_for_next_input()
return "\n".join(all_inputs)
```

<div class="grid cards" markdown>

- :material-hexagon-multiple-outline:{ .lg .middle } __More Example Workflows__
To understand why this happens, see [how a pipeline runs](index.md#how-a-pipeline-runs).

---
If `NodeD` needs to see both `NodeB` and `NodeC` before it does its real work — merging both branches exactly once, rather than running twice — write that logic in a Python node using the `require_node_outputs` or `wait_for_next_input` utility functions. The same functions handle the related case where a branch is optional and may not run at all. See [Merging Parallel Branches](../../tech-hub/merging_parallel_branches.md) for worked examples of both.

[:octicons-arrow-right-24: Workflow Cookbook](../../how-to/workflow_cookbook.md)
## See also

</div>
- [Workflow Cookbook](../../how-to/workflow_cookbook.md)
- [Merging Parallel Branches](../../tech-hub/merging_parallel_branches.md)
4 changes: 2 additions & 2 deletions docs/concepts/pipelines/router_nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Router nodes are decision points in your pipeline. Instead of following one fixed path, a pipeline with a router can choose different paths based on what the participant says or what your system already knows about the participant.

In simple terms, a router checks the current conversation context and sends the input (participant message plus available data) to the most relevant downstream node. This allows your chatbot to adapt in real time.
In simple terms, a router evaluates a condition, chooses one of its configured paths, and passes the input through unchanged to the node on that path. This allows your chatbot to adapt in real time. What the router evaluates depends on its type — see [Router Types](#router-types) below.

For example:

Expand All @@ -16,7 +16,7 @@ For example:

1. **Linked Downstream Node**: Any node that appears after the current node in the pipeline flow.

2. **Conversation Context**: The total set of information available to the pipeline at that moment. This includes the participants current message, their chat history (as determined by the router's own [History setting](history.md)), and known data (like whether they are a "new" or "returning" participant).
2. **Conversation Context**: The information a router evaluates to make its decision. For an [LLM Router](#llm-router-node), this is the participant's current message and, when enabled by the [History setting](history.md), the configured conversation history. A [Static Router](#static-router-node) does not evaluate the message or history at all — it looks up a value already stored as data (see [Router Types](#router-types)).

3. **Default Path**: The "safety net" route (marked with a blue *). If the router cannot confidently decide where to send the participant, it follows this path to prevent the conversation from breaking. [Read more about the default output](../../how-to/routers/index.md#the-default-output).

Expand Down
6 changes: 3 additions & 3 deletions docs/concepts/tags.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ Tags can be created using the “Manage Tags” section.

There are 3 types of tags.

* System tags
### System tags
These are tags generated by the system, such as those used in multi-prompt architectures to differentiate between parent and child chatbots.

* Session tags
### Session tags
These tags are manually added to sessions.

* Message tags
### Message tags
These tags are manually added to specific messages within a participant session.

![Tags applied to sessions and messages in Open Chat Studio](../assets/images/tags_screenshot.png)
4 changes: 3 additions & 1 deletion docs/how-to/routers/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ To understand how participants move through your chatbot, you can enable Output
- [Tracing](../../concepts/tracing.md): Configure this for path-level analysis for debugging.

### Tag naming convention
To keep system tags organized, OCS follows this naming convention:
To keep [system tags](../../concepts/tags.md#system-tags) organized, OCS follows this naming convention:

```text
<node_name>:<route_name>
Expand All @@ -34,4 +34,6 @@ To keep system tags organized, OCS follows this naming convention:
Example: If you have a Router node named `support_triage` and it selects the output keyword `BILLING`, the resulting tag is:
`support_triage:BILLING`

If the router falls back to its [Default Output](#the-default-output) — because nothing matched, or an error occurred — OCS appends `:default` to the tag, making the full form `<node_name>:<route_name>:default`. For example, `support_triage:GENERAL:default`. This makes fallback routes easy to filter for separately when reviewing tags.

Ensure your `node_name` is descriptive (for example, `intent_classifier`) so tags are easy to interpret.
1 change: 1 addition & 0 deletions docs/tech-hub/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ You need Super Admin, Pipeline, Experiment, or Team Administrator roles to acces
- **[Custom Actions](custom_action/index.md)** — Integrate external services into chatbots via OpenAPI schemas. Covers configuration, health monitoring, and testing of Custom Actions.
- **[Calling External APIs](external-api-calls/index.md)** — Use the built-in HTTP client inside Python nodes to securely call third-party APIs from a Pipeline workflow.
- **[Python Node](python_node.md)** — Write custom Python code inside a Pipeline to perform logic, process data, manage session state, and make HTTP requests to external services.
- **[Merging Parallel Branches](merging_parallel_branches.md)** — Python node patterns for merging pipeline branches predictably, including branches that run an uneven number of times or run conditionally.
- **[Render a Template and Send an Email Nodes](template_and_email_nodes.md)** — Full Jinja2 variable reference, recipient field syntax, and examples for the Render a Template and Send an Email pipeline nodes.
- **[Tools Reference](tools.md)** — Full argument reference for all built-in tools and the LLM provider tools supported. For a conceptual overview, see [Tools Concepts](../concepts/tools/index.md).
- **[Evaluations](evaluations/index.md)** — Reference for advanced features of Evaluations — a testing system for measuring chatbot performance against different metrics.
Expand Down
Loading