Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions .github/workflows/docs-broken-links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ on:
pull_request:
paths:
- "docs/**"
- "docs.json"
- "docs/docs.json"
push:
branches:
- main
paths:
- "docs/**"
- "docs.json"
- "docs/docs.json"
workflow_dispatch:

jobs:
Expand All @@ -25,11 +25,12 @@ jobs:
with:
node-version: "22"

- name: Install libsecret for Mintlify CLI
run: sudo apt-get update && sudo apt-get install -y libsecret-1-0

- name: Install Mintlify CLI
run: npm i -g mintlify
run: npm install -g mint@latest

- name: Run broken link checker
run: |
# Auto-answer the prompt with yes command
yes "" | mintlify broken-links || test $? -eq 141
working-directory: ./docs
run: mint broken-links
170 changes: 170 additions & 0 deletions docs/en/concepts/flows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

<Note>
When 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.
</Note>

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

<Note>
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.
</Note>

### Human in the Loop (human feedback)

<Note>
Expand Down
Loading