User Feedback

UserFeedbackTools lets an agent pause and ask the user structured questions with predefined options.

UserFeedbackTools exposes a single ask_user tool. The agent presents structured questions with predefined options (single or multi-select), the run pauses, and execution resumes once the user provides their selections. This is a human-in-the-loop pattern for clarifying intent mid-run.

Prerequisites

The following example requires the openai and sqlalchemy libraries.

uv pip install -U agno openai sqlalchemy

Example

cookbook/02_agents/10_human_in_the_loop/user_feedback.py
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIResponses
from agno.tools.user_feedback import UserFeedbackTools

agent = Agent(
    model=OpenAIResponses(id="gpt-5.2"),
    tools=[UserFeedbackTools()],
    instructions=[
        "You are a helpful travel assistant.",
        "When the user asks you to plan a trip, use the ask_user tool to clarify their preferences.",
    ],
    db=SqliteDb(db_file="tmp/user_feedback.db"),
    markdown=True,
)

run_response = agent.run("Help me plan a vacation")

Resolving the Pause

When the agent calls ask_user, the run pauses with a user_feedback_schema. Collect the user's selections and continue the run:

while run_response.is_paused:
    for requirement in run_response.active_requirements:
        if requirement.needs_user_feedback:
            selections = {}
            for question in requirement.user_feedback_schema or []:
                print(f"\n{question.header}: {question.question}")
                for number, option in enumerate(question.options, 1):
                    print(f"  {number}. {option.label}: {option.description or ''}")
                while True:
                    raw = input("Choose number(s), separated by commas: ")
                    parts = [part.strip() for part in raw.split(",")]
                    if not all(part.isdigit() for part in parts):
                        print("Enter the displayed option numbers.")
                        continue
                    indices = list(dict.fromkeys(int(part) - 1 for part in parts))
                    if not indices or any(i < 0 or i >= len(question.options) for i in indices):
                        print("Choose from the displayed options.")
                        continue
                    if not question.multi_select and len(indices) != 1:
                        print("Choose one option for this question.")
                        continue
                    selections[question.question] = [question.options[i].label for i in indices]
                    break
            requirement.provide_user_feedback(selections)

    run_response = agent.continue_run(
        run_id=run_response.run_id,
        requirements=run_response.requirements,
    )

After the loop, display run_response.content. This collector expects the model to provide the question options described below.

Question Schema

ask_user accepts a list of AskUserQuestion objects:

FieldTypeDescription
questionstrThe question. Must end with a question mark.
headerstrShort label (max 12 chars), e.g. "Destination".
optionsList[AskUserOption]2-4 options to choose from.
multi_selectboolIf True, the user can select multiple options.

Each AskUserOption has a label (1-5 words) and an optional description. These size and wording limits guide the model; the Pydantic fields do not enforce them as validators.

Toolkit Params

ParameterTypeDefaultDescription
instructionsOptional[str]defaultsOverride the default LLM instructions.
add_instructionsboolTrueAdd the instructions to the agent's system message.

Developer Resources