From b690ef69aed36b5ea07d33566cbebb96b94f0d4a Mon Sep 17 00:00:00 2001 From: Iris Clawd Date: Wed, 1 Apr 2026 21:17:42 +0000 Subject: [PATCH] docs: add Conversational Flows (self.ask) section to flows docs Add comprehensive documentation for the self.ask() feature covering: - Basic usage and API - Multiple asks in a single method - Timeout support with retry pattern - Bidirectional metadata support - Custom InputProvider protocol (Slack example) - Auto-checkpoint behavior with persistence - Comparison table: self.ask() vs @human_feedback --- docs/en/concepts/flows.mdx | 170 +++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/docs/en/concepts/flows.mdx b/docs/en/concepts/flows.mdx index defbd3e01..ea482f199 100644 --- a/docs/en/concepts/flows.mdx +++ b/docs/en/concepts/flows.mdx @@ -572,6 +572,176 @@ The `third_method` and `fourth_method` listen to the output of the `second_metho When you run this Flow, the output will change based on the random boolean value generated by the `start_method`. +### Conversational Flows (User Input) + +The `self.ask()` method pauses flow execution to request input from a user inline, then returns their response as a string. This enables conversational, interactive flows where the AI can gather information, ask clarifying questions, or request approvals during execution. + +#### Basic Usage + +```python Code +from crewai.flow.flow import Flow, start, listen + +class GreetingFlow(Flow): + @start() + def greet(self): + name = self.ask("What's your name?") + self.state["name"] = name + + @listen(greet) + def welcome(self): + print(f"Welcome, {self.state['name']}!") + +flow = GreetingFlow() +flow.kickoff() +``` + +By default, `self.ask()` uses a `ConsoleProvider` that prompts via Python's built-in `input()`. + +#### Multiple Asks in One Method + +You can call `self.ask()` multiple times within a single method to gather several inputs: + +```python Code +from crewai.flow.flow import Flow, start + +class OnboardingFlow(Flow): + @start() + def collect_info(self): + name = self.ask("What's your name?") + role = self.ask("What's your role?") + team = self.ask("Which team are you joining?") + self.state["profile"] = {"name": name, "role": role, "team": team} + print(f"Welcome {name}, {role} on {team}!") + +flow = OnboardingFlow() +flow.kickoff() +``` + +#### Timeout Support + +Pass `timeout=` (in seconds) to avoid blocking indefinitely. If the user doesn't respond in time, `self.ask()` returns `None`: + +```python Code +from crewai.flow.flow import Flow, start + +class ApprovalFlow(Flow): + @start() + def request_approval(self): + response = self.ask("Approve deployment? (yes/no)", timeout=120) + + if response is None: + print("No response received — timed out.") + self.state["approved"] = False + return + + self.state["approved"] = response.strip().lower() == "yes" +``` + +Use a `while` loop to retry on timeout: + +```python Code +from crewai.flow.flow import Flow, start + +class RetryFlow(Flow): + @start() + def ask_with_retry(self): + answer = None + while answer is None: + answer = self.ask("Please confirm (yes/no):", timeout=60) + if answer is None: + print("Timed out, asking again...") + self.state["confirmed"] = answer.strip().lower() == "yes" +``` + +#### Metadata Support + +The `metadata` parameter enables bidirectional context passing between the flow and the input provider. Send context to the provider, and receive structured context back: + +```python Code +from crewai.flow.flow import Flow, start + +class ContextualFlow(Flow): + @start() + def gather_feedback(self): + response = self.ask( + "Rate this output (1-5):", + metadata={ + "step": "quality_review", + "output_id": "abc-123", + "options": ["1", "2", "3", "4", "5"], + }, + ) + self.state["rating"] = int(response) if response else None +``` + +When a custom provider returns an `InputResponse`, it can include its own metadata (e.g., user identity, timestamp, channel info) that your flow can process. + +#### Custom InputProvider + +For production use cases (Slack bots, web UIs, webhooks), implement the `InputProvider` protocol: + +```python Code +from crewai.flow.flow import Flow, start +from crewai.flow.input_provider import InputProvider, InputResponse +import requests + +class SlackInputProvider(InputProvider): + def __init__(self, channel_id: str, bot_token: str): + self.channel_id = channel_id + self.bot_token = bot_token + + def request_input(self, message, flow, metadata=None): + # Post the question to Slack + requests.post( + "https://slack.com/api/chat.postMessage", + headers={"Authorization": f"Bearer {self.bot_token}"}, + json={"channel": self.channel_id, "text": message}, + ) + # Wait for and return the user's reply (simplified) + reply = self.poll_for_reply() + return InputResponse( + value=reply["text"], + metadata={"user": reply["user"], "ts": reply["ts"]}, + ) + + def poll_for_reply(self): + # Your implementation to wait for a Slack reply + ... + +# Use the custom provider +flow = Flow(input_provider=SlackInputProvider( + channel_id="C01ABC123", + bot_token="xoxb-...", +)) +flow.kickoff() +``` + +The `request_input` method can return: +- A **string** — used directly as the user's response +- An **`InputResponse`** — includes `value` (the response string) and optional `metadata` +- **`None`** — treated as a timeout / no response + +#### Auto-Checkpoint Behavior + + +When [persistence](/concepts/flows-persistence) is configured, the flow state is automatically saved **before** each `self.ask()` call. If the process restarts while waiting for input, the flow can resume from the checkpoint without losing progress. + + +#### `self.ask()` vs `@human_feedback` + +| | `self.ask()` | `@human_feedback` | +|---|---|---| +| **Purpose** | Inline user input during execution | Approval gates and review feedback | +| **Returns** | `str \| None` | `HumanFeedbackResult` with structured fields | +| **Timeout** | Built-in `timeout=` parameter | Not built-in | +| **Provider** | Pluggable `InputProvider` protocol | Console-based | +| **Use when** | Gathering data, clarifications, confirmations | Review/approval workflows with structured feedback | +| **Decorator** | None — call `self.ask()` anywhere | `@human_feedback` on the method | + + +Both features coexist — you can use `self.ask()` and `@human_feedback` in the same flow. Use `self.ask()` for inline data gathering and `@human_feedback` for structured review gates. + + ### Human in the Loop (human feedback)