Use this file to discover all available pages before exploring further.
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 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 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 in Agent Studio, including:
A Managed Topic can trigger a flow when it matches a user request. This is the primary way to collect entities from a topic, since Managed Topics do not support entity extraction directly.
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.
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:
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.
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:
Before entering the flow, store the topic name in state (e.g. conv.state.original_topic).
In the flow’s exit function, check the stored topic and return a prompt that directs the LLM to the relevant topic content:
What is an “exit function”? There is no special “exit function” step type. An exit function is just a transition function 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.
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.
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.
Anti-pattern: checking the verification result inline after goto_flow().
# Don't do thisdef 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:
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:
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."}
Anti-pattern: if conv.state.is_verified == False:When is_verified has never been written, conv.state.is_verified returns None, not False. None == False is False, so the guard is silently skipped and the unverified user falls through to the protected code.
# Don't do this — silently lets unverified callers throughif conv.state.is_verified == False: ...# Do this — treats missing and falsy the same wayif not conv.state.get("is_verified"): ...
Anti-pattern: mutual recursion between a protected function and its guard.
# Don't do thisdef 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.
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.
1
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.
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." ...
2
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.
Without the success-path write, the protected function’s is_verified check stays falsy and the loop repeats on every turn.
3
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.
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 below.
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.
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
Inside a function, only the last flow.goto_step(...) call is executed