# 11 Dynamic Selector Script: `example/11_dynamic_selector.py`. OpenRath v1.2.1 adds `flow.Selector`, an LLM-backed routing decision over self-describing workflows. The selector returns a `Workflow`; your Python code decides whether and when to call it. ```python from rath import flow from rath.session import Session selector = flow.Selector(provider) billing = flow.Agent( "Handle billing questions briefly.", provider, description="Billing, invoices, refunds, payment methods", ) technical = flow.Agent( "Solve technical problems briefly.", provider, description="Installation, errors, configuration, troubleshooting", ) session = Session.from_user_message("My invoice was charged twice.").to("local") chosen = selector.forward(session, billing, technical) if not isinstance(chosen, flow.EmptyWorkflow): session = chosen(session) ``` The same decision can drive a bounded loop: ```python rounds = 0 while not isinstance( chosen := selector.forward(session, technical, billing), flow.EmptyWorkflow, ): session = chosen(session) rounds += 1 if rounds >= 4: break ``` | API | v1.2.1 behavior | | --- | --- | | `Workflow(description="...")` | Stores the routing description. `Agent` and `Compressor` expose the same keyword. | | `Selector.forward(session, *workflows)` | Returns one candidate `Workflow` or `EmptyWorkflow`; it does not return a `Session`. | | `EmptyWorkflow.forward(session)` | Returns the same session unchanged. | | `select_session(...)` | Returns `(zero_based_index, description)` or `(-1, "")` and does not create lineage. | OpenRath v1.2.2 adds the `Session.text()` accessor used by the shipped example: ```python print(session.text()) # latest non-empty assistant text, or None ``` `Selector.__init__` still accepts `on_event`, but the v1.3.0 selection call does not forward that callback. Do not rely on streamed selector events in this version. [← Runnable Examples](index.md)