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

# Triggering flows

> Start flows from Managed Topics, global functions, or transition functions using conv.goto_flow().

<Info>
  **This page requires Python familiarity** for the programmatic examples. Non-technical operators can trigger flows from Managed Topics actions without writing code – see [Start a flow from a Managed Topics action](#start-a-flow-from-a-managed-topics-action) below. All code-focused content is also available in the **Developer** tab.
</Info>

A flow starts when something calls `conv.goto_flow("Flow name")`. This may happen through a Managed Topics action or programmatically inside a function.

This page explains how `conv.goto_flow` can be used:

You can call `conv.goto_flow(...)` from any [function](/tools/introduction) in Agent Studio, including:

* A [Managed Topics action](/managed-topics/how-to-setup-action/introduction)
* A [global function](/tools/classes/conv-object)
* A [transition function inside a flow](/flows/transition-functions)

Example:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
def start_booking(conv: Conversation):
    conv.goto_flow("Make a booking")
    return
```

What happens:

* The function runs during the current turn.
* When the turn completes, the agent enters the named flow.
* The flow begins at its configured start step.

## Start a flow from a Managed Topics action

A Managed Topic can trigger a flow when it matches a user request. This is the primary way to collect [entities](/flows/no-code/entities) from a topic, since Managed Topics do not support entity extraction directly.

### Using the /Flow shortcut (no code)

<img src="https://mintcdn.com/polyai/Qu880HppNqT19Eyr/images/flows/create-flow-rich-text.png?fit=max&auto=format&n=Qu880HppNqT19Eyr&q=85&s=d7bdc34772c19319b1567c4ed45af4ee" alt="example-main" width="1691" height="1199" data-path="images/flows/create-flow-rich-text.png" />

Inside a Managed Topics **Actions** field, type `/Flow` and use the (+) option to create or attach a flow. When the topic matches, the agent enters the selected flow at its start step.

No custom function is required if you just need to enter the flow.

### Using a tool call from a topic

If you need additional logic before entering the flow – for example, storing context so the agent can return to the topic afterward – use a tool call action instead:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
def start_verification_flow(conv: Conversation, original_faq: str):
    """
    Enter the verification flow and remember which topic we came from.
    Args:
        original_faq: the name of the topic that triggered this function
    """
    conv.state.original_topic = original_faq
    conv.goto_flow("Verify Identity")
    return
```

You can also trigger a flow from a global function by calling `conv.goto_flow()` directly.

### Returning to a topic after a flow

When a topic triggers a flow (e.g. to collect a date or verify identity), the agent can return to the original topic content after the flow exits. To do this:

1. Before entering the flow, store the topic name in state (e.g. `conv.state.original_topic`).
2. In the flow's exit function, check the stored topic and return a prompt that directs the LLM to the relevant topic content:

<Info>
  **What is an "exit function"?** There is no special "exit function" step type. An exit function is just a [transition function](/flows/transition-functions) on the final step of the flow that calls `conv.exit_flow()` and returns a payload (typically `{"content": ...}` or `{"utterance": ...}`). It runs because the step runs — not because the flow registers an "on-exit hook" — and its return value becomes the function output the agent uses next. Mutating `conv.state` from inside it is the same as from any other function: `conv.state["is_verified"] = True` (or `conv.state.is_verified = True`) takes effect immediately and persists across subsequent turns.
</Info>

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
def exit_and_resume(conv: Conversation, flow: Flow):
    conv.exit_flow()
    if conv.state.original_topic:
        return {
            "content": f"Look for the topic '{conv.state.original_topic}' in your context. "
                       f"Continue answering the caller's original question."
        }
    return {"utterance": "Is there anything else I can help with?"}
```

This pattern avoids a generic "Is there anything else?" when the caller's original question has not yet been answered.

### Common mistakes

`conv.goto_flow()` only **queues** a transition — it does not pause your function, run the target flow inline, and resume. The flow runs on subsequent turns. The following anti-patterns all stem from forgetting that.

<Warning>
  **Anti-pattern: checking the verification result inline after `goto_flow()`.**

  ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # Don't do this
  def identify_and_verify_user(conv: Conversation, account_type: str):
      if conv.state.is_verified == False:
          conv.state.original_topic = "check_account_balance"
          conv.goto_flow("Identify & Verify User")
          # ⚠️ Nothing below runs as you'd expect:
          # - The verification flow hasn't run yet (it runs next turn).
          # - `verification_successful` is undefined.
          # - The branch reading `conv.state.is_verified` runs against
          #   the value from BEFORE the flow.
          if verification_successful:
              conv.state.is_verified = True
              return check_account_balance(conv, conv.state.pending_account_type)
  ```

  **Why it fails**: `conv.goto_flow(...)` returns immediately and the rest of the function executes on the same turn — before the verification flow has had a chance to run. Any code that reads the result of the flow on the same call will see stale or undefined values.

  **Correct pattern**: store context, call `conv.goto_flow(...)`, `return` — and resume in the flow's [exit function](/flows/transition-functions):

  ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  def identify_and_verify_user(conv: Conversation, account_type: str):
      if not conv.state.get("is_verified"):
          conv.state.pending_account_type = account_type
          conv.state.original_topic = "check_account_balance"
          conv.goto_flow("Identify & Verify User")
          return "Tell the user we need to verify their identity first."

      # Already verified — proceed with the original task
      return check_account_balance(conv, account_type)
  ```

  Inside the verification flow, its steps set `conv.state["is_verified"]` (and any other context). The flow's exit function reads that state and routes back:

  ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  def exit_and_resume(conv: Conversation, flow: Flow):
      conv.exit_flow()
      if conv.state.get("is_verified") and conv.state.get("original_topic") == "check_account_balance":
          return {
              "content": (
                  f"The caller has been verified. "
                  f"Resume the '{conv.state.original_topic}' topic "
                  f"with account_type='{conv.state.pending_account_type}'."
              )
          }
      return {"utterance": "Tell the user we could not verify their identity."}
  ```
</Warning>

<Warning>
  **Anti-pattern: `if conv.state.is_verified == False:`**

  When `is_verified` has never been written, [`conv.state.is_verified` returns `None`](/tools/classes/conv-object#state), not `False`. `None == False` is `False`, so the guard is silently skipped and the unverified user falls through to the protected code.

  ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # Don't do this — silently lets unverified callers through
  if conv.state.is_verified == False:
      ...

  # Do this — treats missing and falsy the same way
  if not conv.state.get("is_verified"):
      ...
  ```
</Warning>

<Warning>
  **Anti-pattern: mutual recursion between a protected function and its guard.**

  ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # Don't do this
  def check_account_balance(conv: Conversation, account_type: str):
      if not conv.state.get("is_verified"):
          return identify_and_verify_user(conv)   # recurses; returns None on cold-start
      ...

  def identify_and_verify_user(conv: Conversation):
      if not conv.state.get("is_verified"):
          conv.goto_flow("Identify & Verify User")
          return "Tell the user we need to verify their identity first."
      # Unreachable on the first call — the function above returned already
      return check_account_balance(conv, conv.state.pending_account_type)
  ```

  **Why it fails**: on the first (unverified) call, `identify_and_verify_user` queues `goto_flow` and returns a string — but execution never comes back into `check_account_balance` on this turn, so callers reading the return of `check_account_balance` get `None` or the verification string instead of a balance response.

  **Correct pattern**: only the entry point calls `goto_flow` + `return`. Post-flow resumption is done in the target flow's exit function, not by calling the original function recursively.
</Warning>

## Preventing verification loops

A protected function that calls `conv.goto_flow("Identify & Verify User")` whenever `conv.state.is_verified` is falsy will keep sending the user back into verification — every turn, forever — unless two things are true:

* The verification flow writes `is_verified = True` on the success path.
* The protected function has a terminal branch for repeated failures.

Skip either one and the caller is trapped in the loop. The walkthrough below sets up both halves of the fix.

<Steps>
  <Step title="Hand off cleanly from the protected function">
    The entry point stores any context the flow will need on resume, calls `conv.goto_flow(...)`, and returns. It must not try to read the verification result on the same turn — `goto_flow` only queues the transition.

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    def check_account_balance(conv: Conversation, account_type: str):
        if not conv.state.get("is_verified"):
            conv.state.original_topic = "check_account_balance"
            conv.state.pending_account_type = account_type
            conv.goto_flow("Identify & Verify User")
            return "Tell the user we need to verify their identity first."
        ...
    ```
  </Step>

  <Step title="Set is_verified on success and escalate after repeated failures">
    Inside the verification flow, a transition function on the verification step compares user input to the expected value. On success it writes `is_verified = True`; on failure it increments an attempts counter and routes to escalation once the counter hits 3.

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    def check_verification(conv: Conversation, flow: Flow):
        if conv.state.get("user_input") == conv.state.get("expected_value"):
            conv.state.is_verified = True
            flow.goto_step("Verification succeeded")
            return

        attempts = conv.state.get("verification_attempts", 0) + 1
        conv.state.verification_attempts = attempts
        if attempts >= 3:
            conv.goto_flow("Escalation")
            return
        flow.goto_step("Retry verification")
        return
    ```

    Without the success-path write, the protected function's `is_verified` check stays falsy and the loop repeats on every turn.
  </Step>

  <Step title="Add a terminal branch in the protected function">
    Even with the flow setting `is_verified` correctly, the caller still needs its own guard on the attempts counter. Otherwise a user who fails three times and then returns to the original topic re-triggers verification a fourth time.

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    def check_account_balance(conv: Conversation, account_type: str):
        if conv.state.get("verification_attempts", 0) >= 3:
            return "Tell the user we cannot continue without successful identity verification."
        if not conv.state.get("is_verified"):
            conv.state.original_topic = "check_account_balance"
            conv.state.pending_account_type = account_type
            conv.goto_flow("Identify & Verify User")
            return "Tell the user we need to verify their identity first."
        ...
    ```
  </Step>
</Steps>

For the in-flow version of the same escalation pattern — when the failure happens inside a flow rather than at a protected entry point — see [Escalating after repeated failed verification](#example-escalating-after-repeated-failed-verification) below.

## Start or switch flows from inside another flow

Once a user is inside a flow, most movement should happen using `flow.goto_step(...)`.

That keeps the user inside the same workflow and simply branches to another step.

However, sometimes you need to switch to an entirely different workflow.

Examples:

* The user fails identity verification and must enter a verification flow.
* The user requests a human agent and must enter an escalation flow.
* A compliance rule requires a separate structured process.
* The user changes intent entirely (e.g., from booking to cancellation).

In these cases, start a new flow instead of branching.

You do this by calling `conv.goto_flow("Flow name")` from a function inside the current flow.

### Example: Escalating after repeated failed verification

Imagine a booking flow where the user must confirm their date of birth.
If they fail verification too many times, you want to move them into a dedicated verification flow.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
def check_verification(conv: Conversation, flow: Flow):
    attempts = conv.state.verification_attempts or 0

    if not conv.state.is_verified:
        attempts += 1
        conv.state.verification_attempts = attempts

        if attempts >= 3:
            conv.goto_flow("Identity Verification")
            return

        flow.goto_step("Retry verification")
        return

    flow.goto_step("Continue booking")
    return
```

<Tip> Inside a function, only the last `flow.goto_step(...)` call is executed </Tip>

## Routing based on API results

**Main article**: [`conv.api`](/tools/classes/conv-api)

A function may call an API using `conv.api`.

After receiving a response, explicitly choose one of two actions:

* Stay in the current workflow using `flow.goto_step(...)`
* Switch workflows using `conv.goto_flow(...)`

Example:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
def check_customer_status(conv: Conversation, flow: Flow):
    response = conv.api.crm.lookup_user(id=conv.state.user_id)

    if response.status_code != 200:
        flow.goto_step("API error handling")
        return

    data = response.json()

    if data.get("status") == "requires_verification":
        conv.goto_flow("Verification flow")
        return

    flow.goto_step("Continue booking")
    return
```

## Next steps

<CardGroup cols={3}>
  <Card title="Flows overview" icon="diagram-project" href="/flows/introduction">
    Understand how flows work and the available trigger methods.
  </Card>

  <Card title="Transition functions" icon="code" href="/flows/transition-functions">
    Control routing logic with Python inside flow steps.
  </Card>

  <Card title="Flow object" icon="cube" href="/flows/object">
    Python reference for goto\_step() and current\_step.
  </Card>
</CardGroup>
