> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://poly-ai.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://poly-ai.ferndocs.com/_mcp/server.

# Triggering flows

> Start flows from FAQs, 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 FAQs actions without writing code – see [Start a flow from a FAQs action](#start-a-flow-from-a-faqs-action) below. All code-focused content is also available in the **Developer** tab.

A flow starts when something calls `conv.goto_flow("Flow name")`. This may happen through a FAQs 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](/agent-studio/tools/overview) in Agent Studio, including:

* A [FAQs action](/agent-studio/knowledge/fa-qs/actions/overview)
* A [global function](/agent-studio/tools/classes-and-objects/object)
* A [transition function inside a flow](/advanced/code-driven-flows/transition-functions)

Example:

```python
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 FAQs action

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

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

![example-main](/_fern-files/_img/c4ca9309d36e715c11083a239f6e07e4397ea884f4faf10a1459361465c6a6d6.webp)

Inside a FAQs **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
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](/advanced/code-driven-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.

```python
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
> # 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](/advanced/code-driven-flows/transition-functions):
>
> ```python
> 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
> 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**
>
> **Anti-pattern: `if conv.state.is_verified == False:`**
>
> When `is_verified` has never been written, [`conv.state.is_verified` returns `None`](/agent-studio/tools/classes-and-objects/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
> # 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**
>
> **Anti-pattern: mutual recursion between a protected function and its guard.**
>
> ```python
> # 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.

## 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.

#### 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
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."
    ...
```

#### 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
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.

#### 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
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."
    ...
```

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 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
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

## Routing based on API results

**Main article**: [`conv.api`](/agent-studio/tools/classes-and-objects/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
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

#### [Flows overview](/agent-studio/flows/overview)

Understand how flows work and the available trigger methods.

#### [Transition functions](/advanced/code-driven-flows/transition-functions)

Control routing logic with Python inside flow steps.

#### [Flow object](/advanced/code-driven-flows/object)

Python reference for goto_step() and current_step.